diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index e0d58d03..e04e7c26 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -418,6 +418,8 @@ WidgetYPF.xaml + + diff --git a/ArcFormats/Softpal/ArcVAFS.cs b/ArcFormats/Softpal/ArcVAFS.cs index b53b1c94..71ec60d7 100644 --- a/ArcFormats/Softpal/ArcVAFS.cs +++ b/ArcFormats/Softpal/ArcVAFS.cs @@ -27,6 +27,8 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Text; +using GameRes.Utility; namespace GameRes.Formats.Softpal { @@ -52,6 +54,9 @@ namespace GameRes.Formats.Softpal return null; uint index_offset = 0x10; uint data_offset = file.View.ReadUInt32 (index_offset); + var base_name = Path.GetFileNameWithoutExtension (file.Name).ToUpperInvariant(); + if (0 == data_offset && "TP" == base_name) + return OpenTpArc (file); if (data_offset < index_offset || data_offset >= file.MaxOffset) return null; int count = (int)(data_offset - index_offset) / 4; @@ -59,9 +64,8 @@ namespace GameRes.Formats.Softpal return null; uint next_offset = data_offset; - var base_name = Path.GetFileNameWithoutExtension (file.Name).ToUpperInvariant(); - bool is_audio = "BGM" == base_name; - bool is_pic = "PIC" == base_name; + bool is_bgm = "BGM" == base_name; + bool is_pic = "PIC" == base_name; var dir = new List (count); for (int i = 0; next_offset != file.MaxOffset && i < count; ++i) { @@ -69,29 +73,28 @@ namespace GameRes.Formats.Softpal var name = string.Format("{0}#{1:D5}", base_name, i); var offset = next_offset; next_offset = index_offset == data_offset ? 0 : file.View.ReadUInt32 (index_offset); - if (uint.MaxValue == next_offset) - break; - else if (0 == next_offset) - next_offset = (uint)file.MaxOffset; - else if (next_offset < offset) + if (uint.MaxValue == next_offset || next_offset < offset) break; uint size = next_offset - offset; if (size < 4) continue; Entry entry; if (is_pic) - entry = new Entry { Name = name, Type = "image", Offset = offset }; - else if (is_audio) - entry = new Entry { Name = name + ".wav", Type = "audio", Offset = offset }; + entry = new Entry { Name = name, Type = "image" }; + else if (is_bgm) + entry = new Entry { Name = name + ".wav", Type = "audio" }; else entry = new AutoEntry (name, () => { uint signature = file.View.ReadUInt32 (offset); uint s16 = signature & 0xFFFF; if (1 == s16 || 3 == s16 || 4 == s16) return s_PicFormat.Value; + if (size > 0x200 && (size >> 9) == (signature >> 9)) + return AudioFormat.Wav; return AutoEntry.DetectFileType (signature); - }) { Offset = offset }; + }); + entry.Offset = offset; entry.Size = size; if (!entry.CheckPlacement (file.MaxOffset)) return null; @@ -101,5 +104,187 @@ namespace GameRes.Formats.Softpal return null; return new ArcFile (file, this, dir); } + + ArcFile OpenTpArc (ArcView file) + { + uint index_offset = 0x20; + uint data_offset; + for (;;) + { + data_offset = file.View.ReadUInt32 (index_offset); + if (0 != data_offset) + break; + index_offset += 0x10; + if (0xA010 == index_offset) + return null; + } + if (data_offset >= file.MaxOffset) + return null; + var dir = new List(); + while (index_offset < data_offset) + { + var offset = file.View.ReadUInt32 (index_offset); + if (0 != offset) + { + var name = string.Format("TP#{0:D5}.wav", index_offset/0x10 - 1); + var entry = new Entry { + Name = name, + Type = "audio", + Offset = offset, + Size = 0x402 * file.View.ReadUInt32 (index_offset+4), + }; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + index_offset += 0x10; + } + return new TpArchive (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + if (arc is TpArchive) + return OpenVoiceEntry (arc, entry); + else if ("audio" == entry.Type) + return OpenAudioEntry (arc, entry); + else + return base.OpenEntry (arc, entry); + } + + Stream OpenAudioEntry (ArcFile arc, Entry entry) + { + var offset = entry.Offset; + int size = arc.File.View.ReadInt32 (offset); + offset += 4; + int chunk_count = size >> 9; + int pcm_size = chunk_count * 0x400; + var output = new MemoryStream (0x24 + pcm_size); + using (var wav = new BinaryWriter (output, Encoding.ASCII, true)) + { + WriteWavHeader (wav, 2, 22050, pcm_size); + var buffer = new byte[0x204]; + for (int chunk = 0; chunk < chunk_count; ++chunk) + { + arc.File.View.Read (offset, buffer, 0, 0x204); + offset += 0x204; + int src = 0; + var data_offset = wav.BaseStream.Position; + for (int channel = 0; channel < 2; ++channel) + { + int addend = buffer[src++] << 8; + int pcm = LittleEndian.ToInt16 (buffer, src); + src += 2; + wav.Write ((short)pcm); + wav.BaseStream.Position = data_offset + channel * 2; + for (int i = 0; i < 255; ++i) + { + byte v = buffer[src++]; + int diff = v + addend; + pcm += WaveTable1.Value[diff]; + if (pcm > 32767) + pcm = 32767; + else if (pcm < -32767) + pcm = -32767; + wav.Write ((short)pcm); + wav.BaseStream.Seek (2, SeekOrigin.Current); + addend += WaveTable2.Value[v]; + if (addend < 0) + addend = 0; + else if (addend >= 16384) + addend = 16128; + } + wav.BaseStream.Seek (0, SeekOrigin.End); + } + } + } + output.Position = 0; + return output; + } + + Stream OpenVoiceEntry (ArcFile arc, Entry entry) + { + int remaining = (int)entry.Size; + int chunk_count = remaining / 0x402; + int pcm_size = chunk_count * 0x800; + var output = new MemoryStream (0x24 + pcm_size); + var offset = entry.Offset; + using (var wav = new BinaryWriter (output, Encoding.ASCII, true)) + { + WriteWavHeader (wav, 1, 22050, pcm_size); + var buffer = new byte[0x402]; + while (remaining > 0) + { + arc.File.View.Read (offset, buffer, 0, 0x402); + offset += 0x402; + remaining -= 0x402; + int pcm = LittleEndian.ToInt16 (buffer, 1); + int addend = buffer[0] << 8; + wav.Write ((short)pcm); + for (int src = 3; src < buffer.Length; ++src) + { + byte v = buffer[src]; + int diff = v + addend; + pcm += WaveTable1.Value[diff]; + if (pcm > 32767) + pcm = 32767; + else if (pcm < -32767) + pcm = -32767; + wav.Write ((short)pcm); + addend += WaveTable2.Value[v]; + if (addend < 0) + addend = 0; + else if (addend >= 16384) + addend = 16128; + } + } + } + output.Position = 0; + return output; + } + + void WriteWavHeader (BinaryWriter wav, short channels, int freq, int pcm_size) + { + wav.Write ("RIFF".ToCharArray()); + wav.Write (0x24 + pcm_size); + wav.Write ("WAVE".ToCharArray()); + wav.Write ("fmt ".ToCharArray()); + wav.Write (0x10); + wav.Write ((ushort)1); + wav.Write ((ushort)channels); + wav.Write (freq); + wav.Write (freq*channels*2); + wav.Write ((ushort)(channels*2)); + wav.Write ((ushort)16); + wav.Write ("data".ToCharArray()); + wav.Write (pcm_size); + } + + static readonly Lazy WaveTable1 = new Lazy (() => LoadWaveTable ("WaveTable1")); + static readonly Lazy WaveTable2 = new Lazy (() => LoadWaveTable ("WaveTable2")); + + static short[] LoadWaveTable (string name) + { + + var assembly = typeof(VafsOpener).Assembly; + using (var stream = assembly.GetManifestResourceStream ("GameRes.Formats.Softpal."+name)) + { + if (null == stream) + return null; + var src = new byte[stream.Length]; + stream.Read (src, 0, src.Length); + var array = new short[src.Length/2]; + Buffer.BlockCopy (src, 0, array, 0, src.Length); + return array; + } + } + } + + internal class TpArchive : ArcFile + { + public TpArchive (ArcView arc, ArchiveFormat impl, ICollection dir) + : base (arc, impl, dir) + { + } } } diff --git a/ArcFormats/Softpal/WaveTable1 b/ArcFormats/Softpal/WaveTable1 new file mode 100644 index 00000000..d59541cd Binary files /dev/null and b/ArcFormats/Softpal/WaveTable1 differ diff --git a/ArcFormats/Softpal/WaveTable2 b/ArcFormats/Softpal/WaveTable2 new file mode 100644 index 00000000..ad4c9ddb Binary files /dev/null and b/ArcFormats/Softpal/WaveTable2 differ