From 0f281d1c2313e9b781cc2524fa151ca6447b264b Mon Sep 17 00:00:00 2001 From: UlyssesWu Date: Sun, 10 Jan 2021 00:30:10 +0800 Subject: [PATCH] Support A8L8_SW, Fix #51 --- FreeMote.Plugins/Audio/VagDecoder.cs | 331 +++++++++++++++++++++++ FreeMote.Plugins/FreeMote.Plugins.csproj | 1 + FreeMote.Psb/IPsbType.cs | 6 +- FreeMote.sln.DotSettings | 1 + FreeMote/PsbEnums.cs | 4 + FreeMote/PsbExtension.cs | 13 + FreeMote/RL.cs | 8 + README.md | 2 +- 8 files changed, 362 insertions(+), 4 deletions(-) create mode 100644 FreeMote.Plugins/Audio/VagDecoder.cs diff --git a/FreeMote.Plugins/Audio/VagDecoder.cs b/FreeMote.Plugins/Audio/VagDecoder.cs new file mode 100644 index 0000000..3d55b49 --- /dev/null +++ b/FreeMote.Plugins/Audio/VagDecoder.cs @@ -0,0 +1,331 @@ +// Originally by SilicaAndPina & daemon1, modified by UlyssesWu +// References: +// https://github.com/vgmstream/vgmstream/blob/9e971af29239e79c387ea709ccebd9d72bb12ce9/src/meta/vag.c +// https://bitbucket.org/SilicaAndPina/cxml-decompiler/raw/7a0e1a553bfdad369cf09957652b652ab1452d3f/AppInfoCli/VAG.cs + +using System; +using System.IO; +using System.Linq; +using System.Text; +using FreeMote.Psb; + +namespace FreeMote.Plugins.Audio +{ + class VagFile + { + //In fact there are many variations with different signatures. If someday I have to support them but I get bored, I will make a wrapper for vgmstream + private static readonly byte[] Signature = { (byte)'V', (byte)'A', (byte)'G', (byte)'p' }; + + private static readonly int[,] HEVAGCoeffTable = { + { 0, 0, 0, 0 }, + { 7680, 0, 0, 0 }, + { 14720, -6656, 0, 0 }, + { 12544, -7040, 0, 0 }, + { 15616, -7680, 0, 0 }, + { 14731, -7059, 0, 0 }, + { 14507, -7366, 0, 0 }, + { 13920, -7522, 0, 0 }, + { 13133, -7680, 0, 0 }, + { 12028, -7680, 0, 0 }, + { 10764, -7680, 0, 0 }, + { 9359, -7680, 0, 0 }, + { 7832, -7680, 0, 0 }, + { 6201, -7680, 0, 0 }, + { 4488, -7680, 0, 0 }, + { 2717, -7680, 0, 0 }, + { 910, -7680, 0, 0 }, + { -910, -7680, 0, 0 }, + { -2717, -7680, 0, 0 }, + { -4488, -7680, 0, 0 }, + { -6201, -7680, 0, 0 }, + { -7832, -7680, 0, 0 }, + { -9359, -7680, 0, 0 }, + { -10764, -7680, 0, 0 }, + { -12028, -7680, 0, 0 }, + { -13133, -7680, 0, 0 }, + { -13920, -7522, 0, 0 }, + { -14507, -7366, 0, 0 }, + { -14731, -7059, 0, 0 }, + { 5376, -9216, 3328, -3072 }, + { -6400, -7168, -3328, -2304 }, + { -10496, -7424, -3584, -1024 }, + { -167, -2722, -494, -541 }, + { -7430, -2221, -2298, 424 }, + { -8001, -3166, -2814, 289 }, + { 6018, -4750, 2649, -1298 }, + { 3798, -6946, 3875, -1216 }, + { -8237, -2596, -2071, 227 }, + { 9199, 1982, -1382, -2316 }, + { 13021, -3044, -3792, 1267 }, + { 13112, -4487, -2250, 1665 }, + { -1668, -3744, -6456, 840 }, + { 7819, -4328, 2111, -506 }, + { 9571, -1336, -757, 487 }, + { 10032, -2562, 300, 199 }, + { -4745, -4122, -5486, -1493 }, + { -5896, 2378, -4787, -6947 }, + { -1193, -9117, -1237, -3114 }, + { 2783, -7108, -1575, -1447 }, + { -7334, -2062, -2212, 446 }, + { 6127, -2577, -315, -18 }, + { 9457, -1858, 102, 258 }, + { 7876, -4483, 2126, -538 }, + { -7172, -1795, -2069, 482 }, + { -7358, -2102, -2233, 440 }, + { -9170, -3509, -2674, -391 }, + { -2638, -2647, -1929, -1637 }, + { 1873, 9183, 1860, -5746 }, + { 9214, 1859, -1124, -2427 }, + { 13204, -3012, -4139, 1370 }, + { 12437, -4792, -256, 622 }, + { -2653, -1144, -3182, -6878 }, + { 9331, -1048, -828, 507 }, + { 1642, -620, -946, -4229 }, + { 4246, -7585, -533, -2259 }, + { -8988, -3891, -2807, 44 }, + { -2562, -2735, -1730, -1899 }, + { 3182, -483, -714, -1421 }, + { 7937, -3844, 2821, -1019 }, + { 10069, -2609, 314, 195 }, + { 8400, -3297, 1551, -155 }, + { -8529, -2775, -2432, -336 }, + { 9477, -1882, 108, 256 }, + { 75, -2241, -298, -6937 }, + { -9143, -4160, -2963, 5 }, + { -7270, -1958, -2156, 460 }, + { -2740, 3745, 5936, -1089 }, + { 8993, 1948, -683, -2704 }, + { 13101, -2835, -3854, 1055 }, + { 9543, -1961, 130, 250 }, + { 5272, -4270, 3124, -3157 }, + { -7696, -3383, -2907, -456 }, + { 7309, 2523, 434, -2461 }, + { 10275, -2867, 391, 172 }, + { 10940, -3721, 665, 97 }, + { 24, -310, -1262, 320 }, + { -8122, -2411, -2311, -271 }, + { -8511, -3067, -2337, 163 }, + { 326, -3846, 419, -933 }, + { 8895, 2194, -541, -2880 }, + { 12073, -1876, -2017, -601 }, + { 8729, -3423, 1674, -169 }, + { 12950, -3847, -3007, 1946 }, + { 10038, -2570, 302, 198 }, + { 9385, -2757, 1008, 41 }, + { -4720, -5006, -2852, -1161 }, + { 7869, -4326, 2135, -501 }, + { 2450, -8597, 1299, -2780 }, + { 10192, -2763, 360, 181 }, + { 11313, -4213, 833, 53 }, + { 10154, -2716, 345, 185 }, + { 9638, -1417, -737, 482 }, + { 3854, -4554, 2843, -3397 }, + { 6699, -5659, 2249, -1074 }, + { 11082, -3908, 728, 80 }, + { -1026, -9810, -805, -3462 }, + { 10396, -3746, 1367, -96 }, + { 10287, 988, -1915, -1437 }, + { 7953, 3878, -764, -3263 }, + { 12689, -3375, -3354, 2079 }, + { 6641, 3166, 231, -2089 }, + { -2348, -7354, -1944, -4122 }, + { 9290, -4039, 1885, -246 }, + { 4633, -6403, 1748, -1619 }, + { 11247, -4125, 802, 61 }, + { 9807, -2284, 219, 222 }, + { 9736, -1536, -706, 473 }, + { 8440, -3436, 1562, -176 }, + { 9307, -1021, -835, 509 }, + { 1698, -9025, 688, -3037 }, + { 10214, -2791, 368, 179 }, + { 8390, 3248, -758, -2989 }, + { 7201, 3316, 46, -2614 }, + { -88, -7809, -538, -4571 }, + { 6193, -5189, 2760, -1245 }, + { 12325, -1290, -3284, 253 }, + { 13064, -4075, -2824, 1877 }, + { 5333, 2999, 775, -1132 }, +}; + + public string FilePath { get; set; } + public byte ChannelCount { get; set; } + public uint WaveformDataSize { get; set; } + public uint SampleRate { get; set; } + public bool IsStereo => ChannelCount > 1; + public string FileName { get; set; } + public bool IsHEVAG { get; set; } = true; + + public byte[] PcmData { get; set; } + + /* version used to create the file: + * - 00000000 = v1.8 PC, + * - 00000002 = v1.3 Mac (used?) + * - 00000003 = v1.6+ Mac + * - 00000020 = v2.0 PC (most common) + * - 00000004 = ? (later games) + * - 00000006 = ? (vagconv) + * - 00020001 = v2.1 (vagconv2) + * - 00030000 = v3.0 (vagconv2) */ + public uint Version { get; set; } + + public VagFile(string path) + { + FilePath = path; + } + + public bool Load() + { + if (!File.Exists(FilePath)) + { + return false; + } + + using BinaryReader br = new BinaryReader(File.OpenRead(FilePath)); + if (!br.ReadBytes(4).SequenceEqual(Signature)) //0x00 + { + //throw new Exception("Not a VAG file."); + return false; + } + + var fileSize = br.BaseStream.Length; + + Version = br.ReadUInt32BE(); //0x04 + br.ReadBytes(4); //0x08, reserved + WaveformDataSize = br.ReadUInt32BE(); //0x0c + SampleRate = br.ReadUInt32BE(); //0x10 + br.ReadBytes(10); //0x14-0x1D, reserved + ChannelCount = br.ReadByte(); //0x1E, reserved + br.ReadByte(); //0x1F, reserved + FileName = Encoding.ASCII.GetString(br.ReadBytes(16)); //0x20-0x30, file name + br.ReadBytes(16); //0x30-0x40, usually 0 + + // Get PCM data + int Hist = 0; + int Hist2 = 0; + int Hist3 = 0; + int Hist4 = 0; + + using MemoryStream PCMStream = new MemoryStream(); + using BinaryWriter PCMWriter = new BinaryWriter(PCMStream); + + while (br.BaseStream.Position < fileSize) + { + byte DecodingCoefficent = br.ReadByte(); + int ShiftBy = DecodingCoefficent & 0xf; + int PredictNr = DecodingCoefficent >> 0x4; + byte LoopData = br.ReadByte(); + PredictNr |= LoopData & 0xF0; + int LoopFlag = LoopData & 0xf; + if (LoopFlag == 0x7) + { + br.BaseStream.Seek(14, SeekOrigin.Current); + Hist = 0; + Hist2 = 0; + Hist3 = 0; + Hist4 = 0; + } + else + { + + for (int i = 0; i < 14; i++) + { + byte ADPCMData = br.ReadByte(); + int SampleFlags = ADPCMData & 0xF; + int Coefficent; + short Sample; + + for (int ii = 0; ii <= 1; ii++) + { + if (SampleFlags > 7) + { + SampleFlags -= 16; + } + + if (PredictNr < 128) + { + Coefficent = Hist * HEVAGCoeffTable[PredictNr, 0] + Hist2 * HEVAGCoeffTable[PredictNr, 1] + Hist3 * HEVAGCoeffTable[PredictNr, 2] + Hist4 * HEVAGCoeffTable[PredictNr, 3]; + } + else + { + Coefficent = 0; + } + + Sample = (short)(Coefficent / 32 + (SampleFlags << 20 - ShiftBy) + 128 >> 8); + + PCMWriter.Write(Sample); + + + Hist4 = Hist3; + Hist3 = Hist2; + Hist2 = Hist; + Hist = Sample; + + SampleFlags = ADPCMData >> 4; + } + } + } + + /* TODO: + * Arg im mad because i know how to get left/right channels + * But i have no idea how to combine them + * So lets just get one and call it a day. + */ + + if (IsStereo) + { + br.BaseStream.Seek(16, SeekOrigin.Current); + } + } + + + PCMStream.Seek(0x00, SeekOrigin.Begin); + PcmData = PCMStream.ToArray(); + return true; + } + + public void WriteToWavFile(string path) + { + var stream = ToWave(); + if (stream == null) + { + return; + } + + File.WriteAllBytes(path, stream.ToArray()); + } + + public MemoryStream ToWave() + { + if (PcmData == null) + { + return null; + } + + MemoryStream ms = new MemoryStream(); + int fileSize = PcmData.Length; + BinaryWriter bw = new BinaryWriter(ms); + + bw.WriteUTF8("RIFF"); + bw.Write(fileSize + 36); + + bw.WriteUTF8("WAVE"); + bw.WriteUTF8("fmt "); + bw.Write(16); + + bw.Write((short)1); + var channelCount = ChannelCount > 1 ? 2 : 1; + bw.Write((short)channelCount); + bw.Write(SampleRate); + bw.Write((SampleRate * 16 * channelCount) / 8); + bw.Write((short)(16 * channelCount)); + bw.Write((short)16); + + bw.WriteUTF8("data"); + bw.Write(fileSize); + ms.Write(PcmData, 0x00, fileSize); + + ms.Seek(0x00, SeekOrigin.Begin); + return ms; + } + } +} diff --git a/FreeMote.Plugins/FreeMote.Plugins.csproj b/FreeMote.Plugins/FreeMote.Plugins.csproj index 5889a26..53debb5 100644 --- a/FreeMote.Plugins/FreeMote.Plugins.csproj +++ b/FreeMote.Plugins/FreeMote.Plugins.csproj @@ -69,6 +69,7 @@ + diff --git a/FreeMote.Psb/IPsbType.cs b/FreeMote.Psb/IPsbType.cs index eb46e5d..5350357 100644 --- a/FreeMote.Psb/IPsbType.cs +++ b/FreeMote.Psb/IPsbType.cs @@ -75,7 +75,6 @@ public partial class PSB { internal static readonly Dictionary TypeHandlers = new Dictionary() { - {PsbType.PSB, new MotionType()}, //assume as motion type by default {PsbType.Motion, new MotionType()}, {PsbType.Scn, new ScnType()}, {PsbType.Tachie, new ImageType()}, @@ -83,8 +82,9 @@ public partial class PSB {PsbType.Mmo, new MmoType()}, {PsbType.ArchiveInfo, new ArchiveType()}, {PsbType.SoundArchive, new SoundArchiveType()}, - {PsbType.BmpFont, new FontType()} - }; + {PsbType.BmpFont, new FontType()}, + {PsbType.PSB, new MotionType()}, //assume as motion type by default, must put this after Motion + }; } } diff --git a/FreeMote.sln.DotSettings b/FreeMote.sln.DotSettings index b223de6..1a8ddb3 100644 --- a/FreeMote.sln.DotSettings +++ b/FreeMote.sln.DotSettings @@ -7,6 +7,7 @@ True True True + True True True True diff --git a/FreeMote/PsbEnums.cs b/FreeMote/PsbEnums.cs index 82dae37..ce8e37d 100644 --- a/FreeMote/PsbEnums.cs +++ b/FreeMote/PsbEnums.cs @@ -230,6 +230,10 @@ public enum PsbPixelFormat /// RGBA5650 with Swizzle for vita /// RGBA5650_SW, + /// + /// A8L8 with Swizzle for vita + /// + A8L8_SW, } public enum PsbAudioFormat diff --git a/FreeMote/PsbExtension.cs b/FreeMote/PsbExtension.cs index a23184c..bea81a5 100644 --- a/FreeMote/PsbExtension.cs +++ b/FreeMote/PsbExtension.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Drawing.Imaging; using System.IO; @@ -200,6 +201,8 @@ public static PsbPixelFormat ToPsbPixelFormat(this string typeStr, PsbSpec spec) return PsbPixelFormat.LeRGBA4444_SW; case "A8L8": return PsbPixelFormat.A8L8; + case "A8L8_SW": + return PsbPixelFormat.A8L8_SW; case "RGBA5650": return PsbPixelFormat.RGBA5650; case "RGBA5650_SW": @@ -299,6 +302,16 @@ public static string ReadStringZeroTrim(this BinaryReader br) return str; } + public static uint ReadUInt32BE(this BinaryReader br) + { + return BinaryPrimitives.ReadUInt32BigEndian(br.ReadBytes(4)); + } + + public static int ReadInt32BE(this BinaryReader br) + { + return BinaryPrimitives.ReadInt32BigEndian(br.ReadBytes(4)); + } + public static void WriteStringZeroTrim(this BinaryWriter bw, string str) { //bw.Write(str.ToCharArray()); diff --git a/FreeMote/RL.cs b/FreeMote/RL.cs index f59688b..a21fec8 100644 --- a/FreeMote/RL.cs +++ b/FreeMote/RL.cs @@ -46,6 +46,10 @@ public static Bitmap ConvertToImage(byte[] data, int height, int width, case PsbPixelFormat.A8L8: data = ReadA8L8(data, width, height); break; + case PsbPixelFormat.A8L8_SW: + data = ReadA8L8(data, width, height); + data = PostProcessing.UnswizzleTexture(data, width, height, PixelFormat.Format32bppArgb); + break; case PsbPixelFormat.DXT5: data = DxtUtil.DecompressDxt5(data, width, height); Switch_0_2(ref data); //DXT5(for win) need conversion @@ -133,6 +137,10 @@ private static byte[] PixelBytesFromImage(Bitmap bmp, PsbPixelFormat pixelFormat case PsbPixelFormat.A8L8: result = Argb2A8L8(result); break; + case PsbPixelFormat.A8L8_SW: + result = PostProcessing.SwizzleTexture(result, bmp.Width, bmp.Height, bmp.PixelFormat); + result = Argb2A8L8(result); + break; case PsbPixelFormat.DXT5: //Switch_0_2(ref result); result = DxtUtil.Dxt5Encode(result, bmp.Width, bmp.Height); diff --git a/README.md b/README.md index 172c2f1..f122f5a 100644 --- a/README.md +++ b/README.md @@ -74,5 +74,5 @@ Some outputs of FreeMote (mmo/psd etc.) are transformed from FreeMote code and a * @morkt for [ImageTLG](https://github.com/morkt/GARbro/blob/master/ArcFormats/KiriKiri/ImageTLG.cs), [ArcPSB](https://github.com/morkt/GARbro/blob/master/ArcFormats/Emote/ArcPSB.cs), [PspDecompression](https://github.com/morkt/GARbro/blob/master/ArcFormats/Will/ArcPulltop.cs) code. LICENSE: MIT * @xdaniel & @FireyFly for [PostProcessing](https://github.com/xdanieldzd/GXTConvert/blob/master/GXTConvert/Conversion/PostProcessing.cs) code. LICENSE: MIT * @Nyerguds for [BitmapHelper](https://stackoverflow.com/a/45100442) code. -* @[**HopelessHiro**](https://forums.fuwanovel.net/profile/25739-hoplesshiro/) as sponsor! +* @[**HopelessHiro**](https://forums.fuwanovel.net/profile/25739-hoplesshiro/), @skilittle as sponsors! * All nuget references used in this project.