From 54681da20a8cd18d7082bfcd4d301493da4cded2 Mon Sep 17 00:00:00 2001 From: kolardavid Date: Thu, 11 Jan 2018 20:55:56 +0100 Subject: [PATCH] Added support for ADPCM codec, new features and bugfix in audio player --- Forms/AwcForm.Designer.cs | 130 ++- Forms/AwcForm.cs | 362 ++++---- Forms/AwcForm.resx | 6 + GameFiles/FileTypes/AwcFile.cs | 1486 +++++++++++++++++--------------- 4 files changed, 1113 insertions(+), 871 deletions(-) diff --git a/Forms/AwcForm.Designer.cs b/Forms/AwcForm.Designer.cs index f78d5fa..62f26a6 100644 --- a/Forms/AwcForm.Designer.cs +++ b/Forms/AwcForm.Designer.cs @@ -32,6 +32,9 @@ 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.LabelInfo = new System.Windows.Forms.Label(); + this.LabelTime = new System.Windows.Forms.Label(); + this.StopButton = new System.Windows.Forms.Button(); this.VolumeLabel = new System.Windows.Forms.Label(); this.chbAutoJump = new System.Windows.Forms.CheckBox(); this.PrevButton = new System.Windows.Forms.Button(); @@ -41,13 +44,18 @@ this.PlaylistNameHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.PlaylistTypeHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.PlaylistLengthHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.contextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components); + this.ExportAsWav = new System.Windows.Forms.ToolStripMenuItem(); this.VolumeTrackBar = new System.Windows.Forms.TrackBar(); this.PositionTrackBar = new System.Windows.Forms.TrackBar(); this.DetailsTabPage = new System.Windows.Forms.TabPage(); this.DetailsPropertyGrid = new CodeWalker.WinForms.PropertyGridFix(); this.Timer = new System.Windows.Forms.Timer(this.components); + this.saveFileDialog = new System.Windows.Forms.SaveFileDialog(); + this.PlaylistSizeHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.MainTabControl.SuspendLayout(); this.PlayerTabPage.SuspendLayout(); + this.contextMenuStrip.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.VolumeTrackBar)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.PositionTrackBar)).BeginInit(); this.DetailsTabPage.SuspendLayout(); @@ -61,11 +69,14 @@ this.MainTabControl.Location = new System.Drawing.Point(0, 0); this.MainTabControl.Name = "MainTabControl"; this.MainTabControl.SelectedIndex = 0; - this.MainTabControl.Size = new System.Drawing.Size(576, 358); + this.MainTabControl.Size = new System.Drawing.Size(576, 361); this.MainTabControl.TabIndex = 0; // // PlayerTabPage // + this.PlayerTabPage.Controls.Add(this.LabelInfo); + this.PlayerTabPage.Controls.Add(this.LabelTime); + this.PlayerTabPage.Controls.Add(this.StopButton); this.PlayerTabPage.Controls.Add(this.VolumeLabel); this.PlayerTabPage.Controls.Add(this.chbAutoJump); this.PlayerTabPage.Controls.Add(this.PrevButton); @@ -77,25 +88,54 @@ this.PlayerTabPage.Location = new System.Drawing.Point(4, 22); this.PlayerTabPage.Name = "PlayerTabPage"; this.PlayerTabPage.Padding = new System.Windows.Forms.Padding(3); - this.PlayerTabPage.Size = new System.Drawing.Size(568, 332); + this.PlayerTabPage.Size = new System.Drawing.Size(568, 335); this.PlayerTabPage.TabIndex = 0; this.PlayerTabPage.Text = "Player"; this.PlayerTabPage.UseVisualStyleBackColor = true; // + // LabelInfo + // + this.LabelInfo.AutoSize = true; + this.LabelInfo.Location = new System.Drawing.Point(8, 247); + this.LabelInfo.Name = "LabelInfo"; + this.LabelInfo.Size = new System.Drawing.Size(0, 13); + this.LabelInfo.TabIndex = 12; + // + // LabelTime + // + this.LabelTime.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.LabelTime.Location = new System.Drawing.Point(285, 308); + this.LabelTime.Name = "LabelTime"; + this.LabelTime.Size = new System.Drawing.Size(114, 17); + this.LabelTime.TabIndex = 11; + this.LabelTime.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // StopButton + // + this.StopButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.StopButton.Enabled = false; + this.StopButton.Location = new System.Drawing.Point(211, 304); + this.StopButton.Name = "StopButton"; + this.StopButton.Size = new System.Drawing.Size(31, 23); + this.StopButton.TabIndex = 10; + this.StopButton.Text = "◼"; + this.StopButton.UseVisualStyleBackColor = true; + // // VolumeLabel // + this.VolumeLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.VolumeLabel.AutoSize = true; - this.VolumeLabel.Location = new System.Drawing.Point(414, 305); + this.VolumeLabel.Location = new System.Drawing.Point(405, 308); this.VolumeLabel.Name = "VolumeLabel"; - this.VolumeLabel.Size = new System.Drawing.Size(42, 13); + this.VolumeLabel.Size = new System.Drawing.Size(60, 13); this.VolumeLabel.TabIndex = 9; - this.VolumeLabel.Text = "Volume"; + this.VolumeLabel.Text = "🕩 Volume"; // // chbAutoJump // + this.chbAutoJump.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.chbAutoJump.AutoSize = true; - this.chbAutoJump.Enabled = false; - this.chbAutoJump.Location = new System.Drawing.Point(17, 305); + this.chbAutoJump.Location = new System.Drawing.Point(17, 308); this.chbAutoJump.Name = "chbAutoJump"; this.chbAutoJump.Size = new System.Drawing.Size(108, 17); this.chbAutoJump.TabIndex = 8; @@ -104,34 +144,34 @@ // // PrevButton // - this.PrevButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; - this.PrevButton.Location = new System.Drawing.Point(137, 301); + this.PrevButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.PrevButton.Location = new System.Drawing.Point(137, 304); this.PrevButton.Name = "PrevButton"; this.PrevButton.Size = new System.Drawing.Size(31, 23); this.PrevButton.TabIndex = 2; - this.PrevButton.Text = "<<"; + this.PrevButton.Text = "⏮"; this.PrevButton.UseVisualStyleBackColor = true; this.PrevButton.Click += new System.EventHandler(this.PrevButton_Click); // // NextButton // - this.NextButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; - this.NextButton.Location = new System.Drawing.Point(255, 301); + this.NextButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.NextButton.Location = new System.Drawing.Point(248, 304); this.NextButton.Name = "NextButton"; this.NextButton.Size = new System.Drawing.Size(31, 23); this.NextButton.TabIndex = 4; - this.NextButton.Text = ">>"; + this.NextButton.Text = "⏭"; this.NextButton.UseVisualStyleBackColor = true; this.NextButton.Click += new System.EventHandler(this.NextButton_Click); // // PlayButton // - this.PlayButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; - this.PlayButton.Location = new System.Drawing.Point(174, 301); + this.PlayButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.PlayButton.Location = new System.Drawing.Point(174, 304); this.PlayButton.Name = "PlayButton"; - this.PlayButton.Size = new System.Drawing.Size(75, 23); + this.PlayButton.Size = new System.Drawing.Size(31, 23); this.PlayButton.TabIndex = 3; - this.PlayButton.Text = "Play/Pause"; + this.PlayButton.Text = "▶"; this.PlayButton.UseVisualStyleBackColor = true; this.PlayButton.Click += new System.EventHandler(this.PlayButton_Click); // @@ -143,13 +183,15 @@ this.PlayListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { this.PlaylistNameHeader, this.PlaylistTypeHeader, - this.PlaylistLengthHeader}); + this.PlaylistLengthHeader, + this.PlaylistSizeHeader}); + this.PlayListView.ContextMenuStrip = this.contextMenuStrip; this.PlayListView.FullRowSelect = true; this.PlayListView.HideSelection = false; this.PlayListView.Location = new System.Drawing.Point(6, 6); this.PlayListView.MultiSelect = false; this.PlayListView.Name = "PlayListView"; - this.PlayListView.Size = new System.Drawing.Size(556, 235); + this.PlayListView.Size = new System.Drawing.Size(556, 238); this.PlayListView.TabIndex = 0; this.PlayListView.UseCompatibleStateImageBehavior = false; this.PlayListView.View = System.Windows.Forms.View.Details; @@ -158,7 +200,7 @@ // PlaylistNameHeader // this.PlaylistNameHeader.Text = "Name"; - this.PlaylistNameHeader.Width = 303; + this.PlaylistNameHeader.Width = 260; // // PlaylistTypeHeader // @@ -168,14 +210,29 @@ // PlaylistLengthHeader // this.PlaylistLengthHeader.Text = "Length"; - this.PlaylistLengthHeader.Width = 110; + this.PlaylistLengthHeader.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.PlaylistLengthHeader.Width = 80; + // + // contextMenuStrip + // + this.contextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.ExportAsWav}); + this.contextMenuStrip.Name = "contextMenuStrip1"; + this.contextMenuStrip.Size = new System.Drawing.Size(153, 48); + // + // ExportAsWav + // + this.ExportAsWav.Name = "ExportAsWav"; + this.ExportAsWav.Size = new System.Drawing.Size(152, 22); + this.ExportAsWav.Text = "Export as .wav"; + this.ExportAsWav.Click += new System.EventHandler(this.ExportAsWav_Click); // // VolumeTrackBar // this.VolumeTrackBar.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.VolumeTrackBar.BackColor = System.Drawing.SystemColors.ControlLightLight; this.VolumeTrackBar.LargeChange = 10; - this.VolumeTrackBar.Location = new System.Drawing.Point(455, 301); + this.VolumeTrackBar.Location = new System.Drawing.Point(455, 304); this.VolumeTrackBar.Maximum = 100; this.VolumeTrackBar.Name = "VolumeTrackBar"; this.VolumeTrackBar.Size = new System.Drawing.Size(105, 45); @@ -190,7 +247,7 @@ | System.Windows.Forms.AnchorStyles.Right))); this.PositionTrackBar.BackColor = System.Drawing.SystemColors.ControlLightLight; this.PositionTrackBar.LargeChange = 1000; - this.PositionTrackBar.Location = new System.Drawing.Point(6, 263); + this.PositionTrackBar.Location = new System.Drawing.Point(6, 266); this.PositionTrackBar.Maximum = 1000; this.PositionTrackBar.Name = "PositionTrackBar"; this.PositionTrackBar.Size = new System.Drawing.Size(554, 45); @@ -204,7 +261,7 @@ this.DetailsTabPage.Location = new System.Drawing.Point(4, 22); this.DetailsTabPage.Name = "DetailsTabPage"; this.DetailsTabPage.Padding = new System.Windows.Forms.Padding(3); - this.DetailsTabPage.Size = new System.Drawing.Size(568, 332); + this.DetailsTabPage.Size = new System.Drawing.Size(568, 335); this.DetailsTabPage.TabIndex = 1; this.DetailsTabPage.Text = "Details"; this.DetailsTabPage.UseVisualStyleBackColor = true; @@ -215,27 +272,41 @@ this.DetailsPropertyGrid.HelpVisible = false; this.DetailsPropertyGrid.Location = new System.Drawing.Point(3, 3); this.DetailsPropertyGrid.Name = "DetailsPropertyGrid"; - this.DetailsPropertyGrid.Size = new System.Drawing.Size(562, 326); + this.DetailsPropertyGrid.Size = new System.Drawing.Size(562, 329); this.DetailsPropertyGrid.TabIndex = 0; // // Timer // this.Timer.Enabled = true; + this.Timer.Interval = 25; this.Timer.Tick += new System.EventHandler(this.Timer_Tick); // + // saveFileDialog + // + this.saveFileDialog.DefaultExt = "wav"; + this.saveFileDialog.Filter = "Wave files (*.wav)|*.wav"; + // + // PlaylistSizeHeader + // + this.PlaylistSizeHeader.Text = "Size"; + this.PlaylistSizeHeader.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.PlaylistSizeHeader.Width = 80; + // // AwcForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(576, 358); + this.ClientSize = new System.Drawing.Size(576, 361); this.Controls.Add(this.MainTabControl); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MinimumSize = new System.Drawing.Size(592, 300); this.Name = "AwcForm"; this.Text = "AWC Player - CodeWalker by dexyfex"; this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.AwcForm_FormClosing); this.MainTabControl.ResumeLayout(false); this.PlayerTabPage.ResumeLayout(false); this.PlayerTabPage.PerformLayout(); + this.contextMenuStrip.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.VolumeTrackBar)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.PositionTrackBar)).EndInit(); this.DetailsTabPage.ResumeLayout(false); @@ -261,5 +332,12 @@ private System.Windows.Forms.Timer Timer; private System.Windows.Forms.CheckBox chbAutoJump; private System.Windows.Forms.Label VolumeLabel; + private System.Windows.Forms.Button StopButton; + private System.Windows.Forms.Label LabelTime; + private System.Windows.Forms.Label LabelInfo; + private System.Windows.Forms.ContextMenuStrip contextMenuStrip; + private System.Windows.Forms.ToolStripMenuItem ExportAsWav; + private System.Windows.Forms.SaveFileDialog saveFileDialog; + private System.Windows.Forms.ColumnHeader PlaylistSizeHeader; } } \ No newline at end of file diff --git a/Forms/AwcForm.cs b/Forms/AwcForm.cs index 6f144f5..9cd1282 100644 --- a/Forms/AwcForm.cs +++ b/Forms/AwcForm.cs @@ -1,78 +1,86 @@ -using CodeWalker.GameFiles; -using SharpDX.Multimedia; -using SharpDX.XAudio2; -using System; -using System.IO; -using System.Diagnostics; -using System.Windows.Forms; - -namespace CodeWalker.Forms -{ - public partial class AwcForm : Form - { - public AwcFile Awc { get; set; } - +using CodeWalker.GameFiles; +using SharpDX.Multimedia; +using SharpDX.XAudio2; +using System; +using System.IO; +using System.Diagnostics; +using System.Windows.Forms; + +namespace CodeWalker.Forms +{ + public partial class AwcForm : Form + { + public AwcFile Awc { get; set; } + + private AwcAudio currentAudio; private XAudio2 xAudio2; private MasteringVoice masteringVoice; private AudioBuffer audioBuffer; - private SourceVoice sourceVoice; + private SourceVoice sourceVoice; + + private string fileName; + public string FileName + { + get { return fileName; } + set + { + fileName = value; + UpdateFormTitle(); + } + } + public string FilePath { get; set; } + + private enum PlayerState { Stopped, Playing, Paused }; + private PlayerState playerState = PlayerState.Stopped; + + private Stopwatch playtime; + private int playBeginMs; + private float trackLength; + private bool trackFinished; + + public AwcForm() + { + InitializeComponent(); + + playtime = new Stopwatch(); + } + + private void UpdateFormTitle() + { + Text = fileName + " - AWC Player - CodeWalker by dexyfex"; + } + + public void LoadAwc(AwcFile awc) + { + Awc = awc; + DetailsPropertyGrid.SelectedObject = awc; + + fileName = awc?.Name; + if (string.IsNullOrEmpty(fileName)) + { + fileName = awc?.FileEntry?.Name; + } + + PlayListView.Items.Clear(); + + float totalLength = 0; + if (awc.Audios != null) + { + foreach (var audio in awc.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)); + item.Tag = audio; + totalLength += audio.Length; + } + } + + LabelInfo.Text = awc.Audios.Length.ToString() + " track(s), Length: " + TimeSpan.FromSeconds((float)totalLength).ToString("m\\:ss"); + UpdateFormTitle(); + } - private string fileName; - public string FileName - { - get { return fileName; } - set - { - fileName = value; - UpdateFormTitle(); - } - } - public string FilePath { get; set; } - - private enum PlayerState { Stopped, Playing, Paused }; - private PlayerState playerState = PlayerState.Stopped; - - private Stopwatch playtime; - private float trackLength; - - public AwcForm() - { - InitializeComponent(); - - playtime = new Stopwatch(); - } - - private void UpdateFormTitle() - { - Text = fileName + " - AWC Player - CodeWalker by dexyfex"; - } - - public void LoadAwc(AwcFile awc) - { - Awc = awc; - DetailsPropertyGrid.SelectedObject = awc; - - fileName = awc?.Name; - if (string.IsNullOrEmpty(fileName)) - { - fileName = awc?.FileEntry?.Name; - } - - PlayListView.Items.Clear(); - if (awc.Audios != null) - { - foreach (var audio in awc.Audios) - { - var item = PlayListView.Items.Add(audio.Name); - item.SubItems.Add(audio.Type); - item.SubItems.Add(audio.LengthStr); - item.Tag = audio; - } - } - - UpdateFormTitle(); - } - private void Stop() { if (playerState != PlayerState.Stopped) @@ -82,8 +90,8 @@ namespace CodeWalker.Forms audioBuffer.Stream.Dispose(); SetPlayerState(PlayerState.Stopped); } - } - + } + private void SetPlayerState(PlayerState newState) { if (playerState != newState) @@ -94,60 +102,88 @@ namespace CodeWalker.Forms if (playerState == PlayerState.Stopped) playtime.Reset(); playtime.Start(); + + PlayButton.Text = "\u275A\u275A"; + StopButton.Enabled = true; + LabelTime.Visible = true; break; - default: + case PlayerState.Paused: playtime.Stop(); + PlayButton.Text = "\u25B6"; + StopButton.Enabled = true; + LabelTime.Visible = true; + break; + case PlayerState.Stopped: + playtime.Stop(); + PlayButton.Text = "\u25B6"; + LabelTime.Visible = false; + StopButton.Enabled = true; break; } playerState = newState; + UpdateUI(); } - } - - private void InitializeAudio(AwcAudio audio) + } + + private void InitializeAudio(AwcAudio audio, float playBegin = 0) { + currentAudio = audio; trackLength = audio.Length; if (xAudio2 == null) - { - xAudio2 = new XAudio2(); - masteringVoice = new MasteringVoice(xAudio2); + { + xAudio2 = new XAudio2(); + masteringVoice = new MasteringVoice(xAudio2); } Stream wavStream = audio.GetWavStream(); - SoundStream soundStream = new SoundStream(wavStream); - audioBuffer = new AudioBuffer - { - Stream = soundStream.ToDataStream(), - AudioBytes = (int)soundStream.Length, - Flags = BufferFlags.EndOfStream + SoundStream soundStream = new SoundStream(wavStream); + audioBuffer = new AudioBuffer + { + Stream = soundStream.ToDataStream(), + AudioBytes = (int)soundStream.Length, + Flags = BufferFlags.EndOfStream }; + if (playBegin > 0) + { + audioBuffer.PlayBegin = (int)(soundStream.Format.SampleRate * playBegin) / 128 * 128; + if (playtime.IsRunning) + playtime.Restart(); + else + playtime.Reset(); + playBeginMs = (int)(playBegin * 1000); + } + else + playBeginMs = 0; soundStream.Close(); wavStream.Close(); - sourceVoice = new SourceVoice(xAudio2, soundStream.Format, true); + trackFinished = false; + sourceVoice = new SourceVoice(xAudio2, soundStream.Format, true); sourceVoice.SubmitSourceBuffer(audioBuffer, soundStream.DecodedPacketsInfo); - sourceVoice.SetVolume((float)VolumeTrackBar.Value / 100); - } - - private void Play() - { - Stop(); - + sourceVoice.BufferEnd += (context) => trackFinished = true; + sourceVoice.SetVolume((float)VolumeTrackBar.Value / 100); + } + + private void Play() + { + Stop(); + if (PlayListView.SelectedItems.Count == 1) - { - var item = PlayListView.SelectedItems[0]; - var audio = item.Tag as AwcAudio; - + { + var item = PlayListView.SelectedItems[0]; + var audio = item.Tag as AwcAudio; + if (audio != null) { - InitializeAudio(audio); + InitializeAudio(audio); sourceVoice.Start(); SetPlayerState(PlayerState.Playing); } } - } - + } + private void PlayPrevious() { Stop(); @@ -161,8 +197,8 @@ namespace CodeWalker.Forms Play(); } } - } - + } + private void PlayNext() { Stop(); @@ -176,33 +212,36 @@ namespace CodeWalker.Forms Play(); } } - } + } - private void Pause() - { + private void Pause() + { if (playerState == PlayerState.Playing) { - sourceVoice.Stop(); - SetPlayerState(PlayerState.Paused); - } - } - + sourceVoice.Stop(); + SetPlayerState(PlayerState.Paused); + } + } + private void Resume() { if (playerState == PlayerState.Paused) { - sourceVoice.Start(); - SetPlayerState(PlayerState.Playing); + sourceVoice.Start(); + SetPlayerState(PlayerState.Playing); } - } - - private void PositionTrackBar_Scroll(object sender, EventArgs e) - { - - } - - private void PlayButton_Click(object sender, EventArgs e) - { + } + + private void PositionTrackBar_Scroll(object sender, EventArgs e) + { + + sourceVoice.Stop(); + InitializeAudio(currentAudio, PositionTrackBar.Value / 1000); + sourceVoice.Start(); + } + + private void PlayButton_Click(object sender, EventArgs e) + { switch (playerState) { case PlayerState.Stopped: @@ -214,33 +253,44 @@ namespace CodeWalker.Forms case PlayerState.Paused: Resume(); break; - } - } - - private void PrevButton_Click(object sender, EventArgs e) - { - PlayPrevious(); - } - - private void NextButton_Click(object sender, EventArgs e) - { - PlayNext(); - } - - private void VolumeTrackBar_Scroll(object sender, EventArgs e) - { - if (playerState == PlayerState.Playing) - sourceVoice.SetVolume((float)VolumeTrackBar.Value / 100); + } } - private void Timer_Tick(object sender, EventArgs e) + private void PrevButton_Click(object sender, EventArgs e) { + PlayPrevious(); + } + + private void NextButton_Click(object sender, EventArgs e) + { + PlayNext(); + } + + private void VolumeTrackBar_Scroll(object sender, EventArgs e) + { + if (playerState == PlayerState.Playing) + sourceVoice.SetVolume((float)VolumeTrackBar.Value / 100); + } + + private void UpdateUI() + { + if (playerState != PlayerState.Stopped && trackFinished) + { + if (chbAutoJump.Checked) + PlayNext(); + else + Stop(); + } + if (playerState != PlayerState.Stopped) { - int playedMs = (int)playtime.Elapsed.TotalMilliseconds; + int playedMs = (int)playtime.Elapsed.TotalMilliseconds + playBeginMs; int totalMs = (int)(trackLength * 1000); PositionTrackBar.Maximum = totalMs; PositionTrackBar.Value = playedMs < totalMs ? playedMs : totalMs; + + LabelTime.Text = TimeSpan.FromSeconds(playedMs / 1000).ToString("m\\:ss") + + " / " + TimeSpan.FromSeconds(totalMs / 1000).ToString("m\\:ss"); } else { @@ -248,6 +298,11 @@ namespace CodeWalker.Forms } } + private void Timer_Tick(object sender, EventArgs e) + { + UpdateUI(); + } + private void PlayListView_DoubleClick(object sender, EventArgs e) { if (playerState == PlayerState.Playing) @@ -263,6 +318,25 @@ namespace CodeWalker.Forms masteringVoice.Dispose(); xAudio2.Dispose(); } - } - } -} + } + + private void ExportAsWav_Click(object sender, EventArgs e) + { + if (PlayListView.SelectedItems.Count == 1) + { + var item = PlayListView.SelectedItems[0]; + var audio = item.Tag as AwcAudio; + + saveFileDialog.FileName = audio.Name + ".wav"; + if (saveFileDialog.ShowDialog() == DialogResult.OK) + { + Stream wavStream = audio.GetWavStream(); + FileStream stream = File.Create(saveFileDialog.FileName); + wavStream.CopyTo(stream); + stream.Close(); + wavStream.Close(); + } + } + } + } +} diff --git a/Forms/AwcForm.resx b/Forms/AwcForm.resx index 3badf1f..5fa59c9 100644 --- a/Forms/AwcForm.resx +++ b/Forms/AwcForm.resx @@ -117,9 +117,15 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 99, 17 + 17, 17 + + 254, 17 + diff --git a/GameFiles/FileTypes/AwcFile.cs b/GameFiles/FileTypes/AwcFile.cs index aeb64d6..2ac8d17 100644 --- a/GameFiles/FileTypes/AwcFile.cs +++ b/GameFiles/FileTypes/AwcFile.cs @@ -1,708 +1,792 @@ -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; - -namespace CodeWalker.GameFiles -{ - [TC(typeof(EXP))]public class AwcFile : PackedFile - { - public string Name { get; set; } - public RpfFileEntry FileEntry { get; set; } - public byte[] Data { get; set; } - - public string ErrorMessage { get; set; } - - public uint Magic { get; set; } - public ushort Version { get; set; } - public ushort Flags { get; set; } - public int StreamCount { get; set; } - public int InfoOffset { get; set; } - - public bool MultiChannel { get; set; } - public byte[] MultiChannelData { get; set; } - - public AwcStreamInfo[] StreamInfos { get; set; } - public uint[] AudioIds { get; set; } - public AwcAudio[] Audios { get; set; } - - public void Decrypt_RSXXTEA(ref 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) - { - - //adapted from libertyV code - - - //MemoryStream ms = new MemoryStream(data); - Name = entry.Name; - FileEntry = entry; - Data = data; - - 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(ref data, GTA5Keys.PC_AWC_KEY); - Magic = BitConverter.ToUInt32(data, 0); - } 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); - - Magic = r.ReadUInt32(); - Version = r.ReadUInt16(); - Flags = r.ReadUInt16(); - StreamCount = 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) - { - 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); - var infoStart = 16 + (flag0 ? (StreamCount * 2) : 0); - - ms.Position = infoStart; - - List infos = new List(); - Dictionary infoDict = new Dictionary(); - List audioIds = new List(); - List audios = new List(); - - for (int i = 0; i < StreamCount; i++) - { - var info = new AwcStreamInfo(r); - infos.Add(info); - infoDict[info.Id] = info; - } - for (int i = 0; i < StreamCount; i++) - { - var info = infos[i]; - for (int j = 0; j < info.TagCount; j++) - { - var chunk = new AwcChunkInfo(r); - info.Chunks[chunk.Tag] = chunk; - } - } - - StreamInfos = infos.ToArray(); - - - - byte hformat = 0xFA;// 250 0x6061D4FA & 0xFF; //JenkHash.GenHash("format"); - byte hdata = 0x55;// 85 0x5EB5E655 & 0xFF; //JenkHash.GenHash("data"); - byte hycd = 0x5C;// 92 YCD resource chunk... lip sync anims? - byte hunk = 0x36;// 54 unk chunk? small number of bytes (2+) - - - - if (MultiChannel) - { - AwcStreamInfo stream0 = null; - if (!infoDict.TryGetValue(0, out stream0)) - { - ErrorMessage = "Couldn't find MultiChannel stream0"; - return; - } - - AwcChunkInfo chunk72 = null; - if (!stream0.Chunks.TryGetValue(72, out chunk72)) - { - ErrorMessage = "Couldn't find MultiChannel chunk72"; - return; - } - - ms.Position = chunk72.Offset; - - AwcChannelChunkInfo chanInfo = new AwcChannelChunkInfo(r); - if (chanInfo.ChannelCount != StreamCount - 1) - { - ErrorMessage = "Channel Count did not match Stream Count"; - return; - } - - List chunkItems = new List(); - for (int i = 0; i < chanInfo.ChannelCount; i++) - { - var itemInfo = new AwcChannelChunkItemInfo(r); - chunkItems.Add(itemInfo); - audioIds.Add(infos[i + 1].Id); - } - - //AudioStreams.Add(new MultiChannelAudio(new ChunkStream(this.Stream, streamsChunks[0][Tag("data")]), channelsInfoHeader, streamsInfo, header.BigEndian)); - - AwcChunkInfo cdata = null; - if (!stream0.Chunks.TryGetValue(hdata, out cdata)) - { - ErrorMessage = "Couldn't find Stream 0 data chunk"; - return; - } - - ms.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); - ms.Position = cdata.Offset; - - //var d = data;//temporary - - ////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; - //} - - - - } - else - { - - for (int i = 0; i < StreamCount; i++) - { - var info = infos[i]; - - AwcChunkInfo cformat = null; - if (!info.Chunks.TryGetValue(hformat, out cformat)) - { - ErrorMessage = "Couldn't find Stream " + i.ToString() + " format chunk"; - continue; - } - - AwcChunkInfo cdata = null; - if (!info.Chunks.TryGetValue(hdata, out cdata)) - { - ErrorMessage = "Couldn't find Stream " + i.ToString() + " data chunk"; - continue; - } - - AwcChunkInfo cycd = null; - AwcAudioAnimClipDict oycd = null; - if (info.Chunks.TryGetValue(hycd, out cycd)) - { - ms.Position = cycd.Offset; - oycd = new AwcAudioAnimClipDict(r, cycd); - } - - AwcChunkInfo cunk = null; - AwcAudioUnk ounk = null; - if (info.Chunks.TryGetValue(hunk, out cunk)) - { - ms.Position = cunk.Offset; - ounk = new AwcAudioUnk(r, cunk); - } - - - ms.Position = cformat.Offset; - AwcFormatChunk formatChunk = new AwcFormatChunk(r); - - ms.Position = cdata.Offset; - AwcAudio audio = new AwcAudio(r, info, formatChunk, cdata); - - audio.ClipDict = oycd; - audio.UnkData = ounk; - - audios.Add(audio); - audioIds.Add(info.Id); - } - } - - - Audios = audios.ToArray(); - AudioIds = audioIds.ToArray(); - - - } - - } - - } - - - [TC(typeof(EXP))] public class AwcStreamInfo - { - public uint RawVal { get; set; } - public uint TagCount { get; set; } - public uint Id { get; set; } - - public Dictionary Chunks { get; set; } = new Dictionary(); - - public AwcStreamInfo(DataReader r) - { - RawVal = r.ReadUInt32(); - TagCount = (RawVal >> 29); - Id = (RawVal & 0x1FFFFFFF); - } - - public override string ToString() - { - return Id.ToString("X") + ": " + TagCount.ToString() + " tags"; - } - } - - [TC(typeof(EXP))] public class AwcChunkInfo - { - public ulong RawVal { get; set; } - public byte Tag { get; set; } - public int Size { get; set; } - public int Offset { get; set; } - - public AwcChunkInfo(DataReader r) - { - RawVal = r.ReadUInt64(); - Tag = (byte)(RawVal >> 56); - Size = (int)((RawVal >> 28) & 0x0FFFFFFF); - Offset = (int)(RawVal & 0x0FFFFFFF); - } - - public override string ToString() - { - return Tag.ToString() + ": " + Size.ToString() + ", " + Offset.ToString(); - } - } - - [TC(typeof(EXP))] public class AwcChannelChunkInfo - { - public uint Unk0 { get; set; } - public uint ChunkSize { get; set; } - public uint ChannelCount { get; set; } - - public AwcChannelChunkInfo(DataReader r) - { - Unk0 = r.ReadUInt32(); - ChunkSize = r.ReadUInt32(); - ChannelCount = r.ReadUInt32(); - } - - public override string ToString() - { - return Unk0.ToString() + ": " + ChunkSize.ToString() + ", " + ChannelCount.ToString() + " channels"; - } - } - - [TC(typeof(EXP))] public class AwcChannelChunkItemInfo - { - public uint Id { get; set; } - public uint Samples { get; set; } - public ushort Unk0 { get; set; } - public ushort SamplesPerSecond { get; set; } - public byte Unk1 { get; set; } - public byte RoundSize { get; set; } - public ushort Unk2 { get; set; } - - public AwcChannelChunkItemInfo(DataReader r) - { - Id = r.ReadUInt32(); - Samples = r.ReadUInt32(); - Unk0 = r.ReadUInt16(); - SamplesPerSecond = r.ReadUInt16(); - Unk1 = r.ReadByte(); - RoundSize = r.ReadByte(); - Unk2 = r.ReadUInt16(); - } - - public override string ToString() - { - return Id.ToString() + ": " + Samples.ToString() + " samples, " + SamplesPerSecond.ToString() + " samples/sec, size: " + RoundSize.ToString(); - } - } - - [TC(typeof(EXP))] public class AwcFormatChunk - { - public uint Samples { get; set; } - public int LoopPoint { get; set; } - public ushort SamplesPerSecond { get; set; } - public short Headroom { get; set; } - public ushort Unk2 { get; set; } - public ushort Unk3 { get; set; } - public ushort Unk4 { get; set; } - public byte Unk5 { get; set; } - public byte Unk6 { get; set; } - - public AwcFormatChunk(DataReader r) - { - Samples = r.ReadUInt32(); - LoopPoint = r.ReadInt32(); - SamplesPerSecond = r.ReadUInt16(); - Headroom = r.ReadInt16(); - Unk2 = r.ReadUInt16(); - Unk3 = r.ReadUInt16(); - Unk4 = r.ReadUInt16(); - Unk5 = r.ReadByte(); - Unk6 = r.ReadByte(); - - //Apparently sometimes this struct is longer? TODO: fix?? - //r.ReadUInt16(); - //r.ReadUInt16(); - } - - - public override string ToString() - { - return Headroom.ToString() + ", " + Unk6.ToString() + ": " + Samples.ToString() + " samples, " + SamplesPerSecond.ToString() + " samples/sec"; - } - } - - [TC(typeof(EXP))] public class AwcAudio - { - public AwcStreamInfo StreamInfo { get; set; } - public AwcFormatChunk Format { get; set; } - public AwcChunkInfo DataInfo { get; set; } - public byte[] Data { get; set; } - - public AwcAudioAnimClipDict ClipDict { get; set; } - public AwcAudioUnk UnkData { get; set; } - - - public short Channels = 1; - public short BitsPerSample = 4;//16; - public int SamplesPerSecond - { - get - { - return Format?.SamplesPerSecond ?? 0; - } - } - public int SampleCount - { - get - { - return (int)(Format?.Samples ?? 0); - } - } - - - public string Name - { - get - { - return "0x" + StreamInfo?.Id.ToString("X").PadLeft(8, '0') ?? "0"; - } - } - public string Type - { - get - { - if (Format == null) return "Unknown"; - - string fmt = "ADPCM"; - switch (Format.Unk6) - { - case 4: - break; - default: - break; - } - - var hz = Format?.SamplesPerSecond ?? 0; - - return fmt + ((hz > 0) ? (", " + hz.ToString() + " Hz") : ""); - } - } - +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; + +namespace CodeWalker.GameFiles +{ + [TC(typeof(EXP))]public class AwcFile : PackedFile + { + public string Name { get; set; } + public RpfFileEntry FileEntry { get; set; } + public byte[] Data { get; set; } + + public string ErrorMessage { get; set; } + + public uint Magic { get; set; } + public ushort Version { get; set; } + public ushort Flags { get; set; } + public int StreamCount { get; set; } + public int InfoOffset { get; set; } + + public bool MultiChannel { get; set; } + public byte[] MultiChannelData { get; set; } + + public AwcStreamInfo[] StreamInfos { get; set; } + public uint[] AudioIds { get; set; } + public AwcAudio[] Audios { 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) + { + + //adapted from libertyV code + + + //MemoryStream ms = new MemoryStream(data); + Name = entry.Name; + FileEntry = entry; + Data = data; + + 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); + } 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); + + Magic = r.ReadUInt32(); + Version = r.ReadUInt16(); + Flags = r.ReadUInt16(); + StreamCount = 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) + { + 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); + var infoStart = 16 + (flag0 ? (StreamCount * 2) : 0); + + ms.Position = infoStart; + + List infos = new List(); + Dictionary infoDict = new Dictionary(); + List audioIds = new List(); + List audios = new List(); + + for (int i = 0; i < StreamCount; i++) + { + var info = new AwcStreamInfo(r); + infos.Add(info); + infoDict[info.Id] = info; + } + for (int i = 0; i < StreamCount; i++) + { + var info = infos[i]; + for (int j = 0; j < info.TagCount; j++) + { + var chunk = new AwcChunkInfo(r); + info.Chunks[chunk.Tag] = chunk; + } + } + + StreamInfos = infos.ToArray(); + + + + byte hformat = 0xFA;// 250 0x6061D4FA & 0xFF; //JenkHash.GenHash("format"); + byte hdata = 0x55;// 85 0x5EB5E655 & 0xFF; //JenkHash.GenHash("data"); + byte hycd = 0x5C;// 92 YCD resource chunk... lip sync anims? + byte hunk = 0x36;// 54 unk chunk? small number of bytes (2+) + + + + if (MultiChannel) + { + AwcStreamInfo stream0 = null; + if (!infoDict.TryGetValue(0, out stream0)) + { + ErrorMessage = "Couldn't find MultiChannel stream0"; + return; + } + + AwcChunkInfo chunk72 = null; + if (!stream0.Chunks.TryGetValue(72, out chunk72)) + { + ErrorMessage = "Couldn't find MultiChannel chunk72"; + return; + } + + ms.Position = chunk72.Offset; + + AwcChannelChunkInfo chanInfo = new AwcChannelChunkInfo(r); + if (chanInfo.ChannelCount != StreamCount - 1) + { + ErrorMessage = "Channel Count did not match Stream Count"; + return; + } + + List chunkItems = new List(); + for (int i = 0; i < chanInfo.ChannelCount; i++) + { + var itemInfo = new AwcChannelChunkItemInfo(r); + chunkItems.Add(itemInfo); + audioIds.Add(infos[i + 1].Id); + } + + //AudioStreams.Add(new MultiChannelAudio(new ChunkStream(this.Stream, streamsChunks[0][Tag("data")]), channelsInfoHeader, streamsInfo, header.BigEndian)); + + AwcChunkInfo cdata = null; + if (!stream0.Chunks.TryGetValue(hdata, out cdata)) + { + ErrorMessage = "Couldn't find Stream 0 data chunk"; + return; + } + + ms.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); + ms.Position = cdata.Offset; + + //var d = data;//temporary + + ////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; + //} + + + + } + else + { + + for (int i = 0; i < StreamCount; i++) + { + var info = infos[i]; + + AwcChunkInfo cformat = null; + if (!info.Chunks.TryGetValue(hformat, out cformat)) + { + ErrorMessage = "Couldn't find Stream " + i.ToString() + " format chunk"; + continue; + } + + AwcChunkInfo cdata = null; + if (!info.Chunks.TryGetValue(hdata, out cdata)) + { + ErrorMessage = "Couldn't find Stream " + i.ToString() + " data chunk"; + continue; + } + + AwcChunkInfo cycd = null; + AwcAudioAnimClipDict oycd = null; + if (info.Chunks.TryGetValue(hycd, out cycd)) + { + ms.Position = cycd.Offset; + oycd = new AwcAudioAnimClipDict(r, cycd); + } + + AwcChunkInfo cunk = null; + AwcAudioUnk ounk = null; + if (info.Chunks.TryGetValue(hunk, out cunk)) + { + ms.Position = cunk.Offset; + ounk = new AwcAudioUnk(r, cunk); + } + + + ms.Position = cformat.Offset; + AwcFormatChunk formatChunk = new AwcFormatChunk(r); + + ms.Position = cdata.Offset; + AwcAudio audio = new AwcAudio(r, info, formatChunk, cdata); + + audio.ClipDict = oycd; + audio.UnkData = ounk; + + audios.Add(audio); + audioIds.Add(info.Id); + } + } + + + Audios = audios.ToArray(); + AudioIds = audioIds.ToArray(); + + + } + + } + + } + + + [TC(typeof(EXP))] public class AwcStreamInfo + { + public uint RawVal { get; set; } + public uint TagCount { get; set; } + public uint Id { get; set; } + + public Dictionary Chunks { get; set; } = new Dictionary(); + + public AwcStreamInfo(DataReader r) + { + RawVal = r.ReadUInt32(); + TagCount = (RawVal >> 29); + Id = (RawVal & 0x1FFFFFFF); + } + + public override string ToString() + { + return Id.ToString("X") + ": " + TagCount.ToString() + " tags"; + } + } + + [TC(typeof(EXP))] public class AwcChunkInfo + { + public ulong RawVal { get; set; } + public byte Tag { get; set; } + public int Size { get; set; } + public int Offset { get; set; } + + public AwcChunkInfo(DataReader r) + { + RawVal = r.ReadUInt64(); + Tag = (byte)(RawVal >> 56); + Size = (int)((RawVal >> 28) & 0x0FFFFFFF); + Offset = (int)(RawVal & 0x0FFFFFFF); + } + + public override string ToString() + { + return Tag.ToString() + ": " + Size.ToString() + ", " + Offset.ToString(); + } + } + + [TC(typeof(EXP))] public class AwcChannelChunkInfo + { + public uint Unk0 { get; set; } + public uint ChunkSize { get; set; } + public uint ChannelCount { get; set; } + + public AwcChannelChunkInfo(DataReader r) + { + Unk0 = r.ReadUInt32(); + ChunkSize = r.ReadUInt32(); + ChannelCount = r.ReadUInt32(); + } + + public override string ToString() + { + return Unk0.ToString() + ": " + ChunkSize.ToString() + ", " + ChannelCount.ToString() + " channels"; + } + } + + [TC(typeof(EXP))] public class AwcChannelChunkItemInfo + { + public uint Id { get; set; } + public uint Samples { get; set; } + public ushort Unk0 { get; set; } + public ushort SamplesPerSecond { get; set; } + public byte Unk1 { get; set; } + public byte RoundSize { get; set; } + public ushort Unk2 { get; set; } + + public AwcChannelChunkItemInfo(DataReader r) + { + Id = r.ReadUInt32(); + Samples = r.ReadUInt32(); + Unk0 = r.ReadUInt16(); + SamplesPerSecond = r.ReadUInt16(); + Unk1 = r.ReadByte(); + RoundSize = r.ReadByte(); + Unk2 = r.ReadUInt16(); + } + + public override string ToString() + { + return Id.ToString() + ": " + Samples.ToString() + " samples, " + SamplesPerSecond.ToString() + " samples/sec, size: " + RoundSize.ToString(); + } + } + + [TC(typeof(EXP))] public class AwcFormatChunk + { + 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 enum CodecFormat { + PCM = 0, + ADPCM = 4 + } + public CodecFormat Codec { get; set; } + + public AwcFormatChunk(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 = (CodecFormat)r.ReadByte(); + + //Apparently sometimes this struct is longer? TODO: fix?? + //r.ReadUInt16(); + //r.ReadUInt16(); + } + + + public override string ToString() + { + return Headroom.ToString() + ", " + Codec.ToString() + ": " + Samples.ToString() + " samples, " + SamplesPerSecond.ToString() + " samples/sec"; + } + } + + [TC(typeof(EXP))] public class AwcAudio + { + public AwcStreamInfo StreamInfo { get; set; } + public AwcFormatChunk Format { get; set; } + public AwcChunkInfo DataInfo { get; set; } + public byte[] Data { get; set; } + + public AwcAudioAnimClipDict ClipDict { get; set; } + public AwcAudioUnk UnkData { get; set; } + + + public short Channels = 1; + public short BitsPerSample = 16; + public int SamplesPerSecond + { + get + { + return Format?.SamplesPerSecond ?? 0; + } + } + public int SampleCount + { + get + { + return (int)(Format?.Samples ?? 0); + } + } + + + public string Name + { + get + { + return "0x" + StreamInfo?.Id.ToString("X").PadLeft(8, '0') ?? "0"; + } + } + public string Type + { + get + { + if (Format == null) return "Unknown"; + + string codec; + switch (Format.Codec) + { + case AwcFormatChunk.CodecFormat.PCM: + codec = "PCM"; + break; + case AwcFormatChunk.CodecFormat.ADPCM: + codec = "ADPCM"; + break; + default: + codec = "Unknown"; + break; + } + + var hz = Format.SamplesPerSecond; + + return codec + ((hz > 0) ? (", " + hz.ToString() + " Hz") : ""); + } + } + public float Length { - get - { - return Format == null ? 0 : (float)Format.Samples / Format.SamplesPerSecond; + get + { + return Format == null ? 0 : (float)Format.Samples / Format.SamplesPerSecond; } - } - - public string LengthStr - { - get - { - return TimeSpan.FromSeconds(Length).ToString("m\\:ss"); - } - } - - - public AwcAudio(DataReader r, AwcStreamInfo s, AwcFormatChunk f, AwcChunkInfo d) - { - StreamInfo = s; - Format = f; - DataInfo = d; - - Data = r.ReadBytes(d.Size); - } - - public override string ToString() - { - var hash = (StreamInfo?.Id.ToString("X") ?? "0").PadLeft(8, '0'); - return "0x" + hash + ": " + Format?.ToString() ?? "AwcAudio"; - } - - - - public Stream GetWavStream() - { - MemoryStream stream = new MemoryStream(); - BinaryWriter w = new BinaryWriter(stream); - - - //see http://icculus.org/SDL_sound/downloads/external_documentation/wavecomp.htm - //see https://github.com/naudio/NAudio/blob/master/NAudio/Wave/WaveFormats/AdpcmWaveFormat.cs - //see https://msdn.microsoft.com/en-us/library/windows/desktop/ff538799(v=vs.85).aspx - - - int samplesPerSec = SamplesPerSecond; - - Channels = 1; - BitsPerSample = 16; - int byteRate = samplesPerSec * Channels * BitsPerSample / 8; - short blockAlign = (short)(Channels * BitsPerSample / 8); + } + + public string LengthStr + { + get + { + return TimeSpan.FromSeconds(Length).ToString("m\\:ss"); + } + } + + public AwcAudio(DataReader r, AwcStreamInfo s, AwcFormatChunk f, AwcChunkInfo d) + { + StreamInfo = s; + Format = f; + DataInfo = d; + + Data = r.ReadBytes(d.Size); + } + + public override string ToString() + { + var hash = (StreamInfo?.Id.ToString("X") ?? "0").PadLeft(8, '0'); + return "0x" + hash + ": " + Format?.ToString() ?? "AwcAudio"; + } + + public byte[] DecodeADPCM(byte[] data, int sampleCount) + { + byte[] dataPCM = new byte[data.Length * 4]; + int predictor = 0, step_index = 0, step = 0; + int readingOffset = 0, writingOffset = 0, bytesInBlock = 0; + + int[] ima_index_table = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + + 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 + }; + + int clip(int value, int min, int max) + { + if (value < min) + return min; + if (value > max) + return max; + return value; + } + + void parseNibble(byte nibble) + { + step_index = clip(step_index + ima_index_table[nibble], 0, 88); + + int diff = step >> 3; + if ((nibble & 4) > 0) diff += step; + if ((nibble & 2) > 0) diff += step >> 1; + if ((nibble & 1) > 0) diff += step >> 2; + if ((nibble & 8) > 0) predictor -= diff; + else predictor += diff; + + step = ima_step_table[step_index]; + + 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) + { + step_index = clip(data[readingOffset], 0, 88); + predictor = BitConverter.ToInt16(data, readingOffset + 2); + step = ima_step_table[step_index]; + bytesInBlock = 2044; + readingOffset += 4; + } + else + { + parseNibble((byte)(data[readingOffset] & 0x0F)); + parseNibble((byte)((data[readingOffset] >> 4) & 0x0F)); + bytesInBlock--; + sampleCount -= 2; + readingOffset++; + } + } + + return dataPCM; + } + + public Stream GetWavStream() + { + byte[] dataPCM = null; + int bitsPerSample = BitsPerSample; + + switch (Format.Codec) + { + 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); + dataPCM = DecodeADPCM(dataPCM, SampleCount); + bitsPerSample = 16; + break; + } + + 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; // RIFF chunk - var fileSize = 4 + 24 + 8 + Data.Length; - w.Write("RIFF".ToCharArray()); // 0x00 - "RIFF" magic - w.Write(fileSize); // 0x04 - file size - w.Write("WAVE".ToCharArray()); // 0x08 - "WAVE" magic - - // fmt sub-chunk - w.Write("fmt ".ToCharArray()); // 0x0C - "fmt " magic - w.Write(16); // 0x10 - header size (16 bytes) - w.Write((short)1); // 0x14 - audio format (1=PCM) - w.Write(Channels); // 0x16 - number of channels - w.Write(samplesPerSec); // 0x18 - w.Write(byteRate); // 0x1C - w.Write(blockAlign);// sampleSize); // 0x20 - w.Write(BitsPerSample); // 0x22 + w.Write("RIFF".ToCharArray()); + w.Write((int)(wavLength - 8)); + w.Write("WAVE".ToCharArray()); + + // fmt sub-chunk + w.Write("fmt ".ToCharArray()); + w.Write((int)16); // fmt size + w.Write((short)1); // 1 = WAVE_FORMAT_PCM + w.Write((short)Channels); + w.Write((int)SamplesPerSecond); + w.Write((int)byteRate); + w.Write((short)blockAlign); + w.Write((short)bitsPerSample); // data sub-chunk - w.Write("data".ToCharArray()); - w.Write(Data.Length); - w.Write(Data); - - w.Flush(); - - stream.Position = 0; - return stream; - } - - - - } - - [TC(typeof(EXP))] public class AwcAudioAnimClipDict - { - public byte[] Data { get; set; } - public ClipDictionary ClipDict { get; set; } - - public AwcAudioAnimClipDict(DataReader r, AwcChunkInfo info) - { - Data = r.ReadBytes(info.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(); - - - } - - public override string ToString() - { - return (ClipDict?.ClipsMapEntries ?? 0).ToString() + " entries"; - } - } - - [TC(typeof(EXP))] public class AwcAudioUnk - { - public byte[] Data { get; set; } - - public AwcAudioUnk(DataReader r, AwcChunkInfo info) - { - Data = r.ReadBytes(info.Size); - } - - 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 sb.ToString(); - } - } - - - [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 uint TotalDataSize { get; set; } - - public AwcChannelChunk(DataReader r, AwcChannelChunkHeader h, AwcChannelChunkItemInfo i) - { - Header = h; - Info = i; - - TotalDataSize = h.DataSize; - - var rs = i?.RoundSize ?? 0; - int ds = (int)h.DataSize; - if (rs != 0) - { - TotalDataSize = (uint)(TotalDataSize + (((-ds) % rs) + rs) % rs); - } - } - - } - -} + w.Write("data".ToCharArray()); + w.Write((int)dataPCM.Length); + w.Write(dataPCM); + + w.Flush(); + stream.Position = 0; + return stream; + } + } + + [TC(typeof(EXP))] public class AwcAudioAnimClipDict + { + public byte[] Data { get; set; } + public ClipDictionary ClipDict { get; set; } + + public AwcAudioAnimClipDict(DataReader r, AwcChunkInfo info) + { + Data = r.ReadBytes(info.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(); + + + } + + public override string ToString() + { + return (ClipDict?.ClipsMapEntries ?? 0).ToString() + " entries"; + } + } + + [TC(typeof(EXP))] public class AwcAudioUnk + { + public byte[] Data { get; set; } + + public AwcAudioUnk(DataReader r, AwcChunkInfo info) + { + Data = r.ReadBytes(info.Size); + } + + 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 sb.ToString(); + } + } + + + [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 uint TotalDataSize { get; set; } + + public AwcChannelChunk(DataReader r, AwcChannelChunkHeader h, AwcChannelChunkItemInfo i) + { + Header = h; + Info = i; + + TotalDataSize = h.DataSize; + + var rs = i?.RoundSize ?? 0; + int ds = (int)h.DataSize; + if (rs != 0) + { + TotalDataSize = (uint)(TotalDataSize + (((-ds) % rs) + rs) % rs); + } + } + + } + +}