static void Main(string[] args) { if (args.Length < 1) { Console.WriteLine("Syntax: {0} <fsb file> [<output file>]", typeof(Program).Assembly.Location); Console.WriteLine("NOTE: If output file is not given, it will overwrite the given FSB file."); return; } string outputFile = args[args.Length == 2 ? 1 : 0]; var fsb5Header = new FSB5Header(); var mpegData = new List<byte>(); byte[] shdrData; string name = ""; using (var br = new BinaryReader(File.OpenRead(args[0]))) { int sign = CheckSignEndian(br); if (sign < 0) { Console.WriteLine("Invalid file detected."); return; } if (sign != '5') { Console.WriteLine("This tool is designed for FSB5 files only."); return; } int fhSize = ReadFSB5Header(br, fsb5Header); if (fsb5Header.numSamples != 1) { Console.WriteLine("ERROR: Currently this tool only supports FSB5 files that contain a single file within them."); return; } if (fsb5Header.mode != 0x0B) { Console.WriteLine("ERROR: This tool is meant to process MP3-based FSB5s only."); return; } uint nameOffset = (uint)(fhSize + fsb5Header.shdrSize); uint fileOffset = (uint)(nameOffset + fsb5Header.nameSize); uint baseOffset = fileOffset; uint offset = fr32(br); uint type = offset & 0x7F; shdrData = BitConverter.GetBytes(type); shdrData = shdrData.Concat(br.ReadBytes(4)).ToArray(); offset = GET_FSB5_OFFSET(offset); // This is the offset into the file section long currOffset; while ((type & 1) == 1) { uint t32 = fr32(br); shdrData = shdrData.Concat(BitConverter.GetBytes(t32)).ToArray(); type = t32 & 1; int len = (int)((t32 & 0xFFFFFF) >> 1); t32 >>= 24; currOffset = br.BaseStream.Position; shdrData = shdrData.Concat(br.ReadBytes(len)).ToArray(); currOffset += len; br.BaseStream.Position = currOffset; } currOffset = br.BaseStream.Position; uint size; if (br.BaseStream.Position < nameOffset) { size = fr32(br); if (size == 0) size = (uint)br.BaseStream.Length; else size = GET_FSB5_OFFSET(size) + baseOffset; } else size = (uint)br.BaseStream.Length; br.BaseStream.Position = currOffset; fileOffset = baseOffset + offset; size -= fileOffset; if (fsb5Header.nameSize != 0) { currOffset = br.BaseStream.Position; br.BaseStream.Position = nameOffset/* + i * 4*/; br.BaseStream.Position = nameOffset + fr32(br); do { byte c = br.ReadByte(); if (c == 0) break; name += (char)c; } while (true); br.BaseStream.Position = currOffset; } br.BaseStream.Position = currOffset = fileOffset; // Get MPEG data and remove padding long endOffset = fileOffset + size; while (currOffset < endOffset) { var header = br.ReadBytes(4); var mpegVersion = (mpg123_version)(3 - ((header[1] >> 3) & 0x03)); int layer = 4 - ((header[1] >> 1) & 0x03); int bitrateIndex = (header[2] >> 4) & 0x0F; int sampleRateIndex = (header[2] >> 2) & 0x03; int padding = (header[2] >> 1) & 0x01; int bitrate = getMPEGBitrate(mpegVersion, layer, bitrateIndex); int sampleRate = getMPEGSampleRate((int)mpegVersion, sampleRateIndex); int frameLengthInBytes = getMPEGFrameRateInBytes(layer, bitrate, sampleRate, padding); mpegData.AddRange(header); mpegData.AddRange(br.ReadBytes(frameLengthInBytes - 4)); // Check if the next 2 bytes would be an MPEG header, if not, skip to the next 4-byte offset as well as skip any other nul bytes we encounter after that if (br.BaseStream.Position < endOffset && br.PeekByte() != 0xFF && (br.PeekByte(1) & 0xF0) != 0xF0) { br.BaseStream.Position += GetNextMultipleOf4(frameLengthInBytes) - frameLengthInBytes; if (br.BaseStream.Position < endOffset && br.PeekByte() == 0) { while (br.BaseStream.Position < endOffset && br.ReadByte() == 0) ; if (br.BaseStream.Position != endOffset) --br.BaseStream.Position; } } currOffset = br.BaseStream.Position; } } using (var bw = new BinaryWriter(File.Create(outputFile))) { bw.Write(Encoding.ASCII.GetBytes("FSB5")); bw.Write(fsb5Header.version); bw.Write(1); bw.Write(shdrData.Length); int fullNameSize = (int)Math.Ceiling((name.Length + 5) / 16.0) * 16; bw.Write(fullNameSize); bw.Write(mpegData.Count); bw.Write(fsb5Header.mode); if (fsb5Header.version == 0) bw.Write(fsb5Header.extra); bw.Write(fsb5Header.zero); bw.Write(fsb5Header.hash); bw.Write(fsb5Header.dummy); bw.Write(shdrData); bw.Write(4); bw.Write(Encoding.ASCII.GetBytes(name)); bw.Write((byte)0); for (int j = name.Length + 5; j < fullNameSize; ++j) bw.Write((byte)0); bw.Write(mpegData.ToArray()); } if (args.Length == 1) Console.WriteLine("Overwrote {0} with un-padded MPEG FSB5.", outputFile); else Console.WriteLine("Wrote un-padded MPEG FSB5 to {0}.", outputFile); }
static int ReadFSB5Header(BinaryReader br, FSB5Header fsb5Header) { long oldPosition = br.BaseStream.Position; fsb5Header.id = br.ReadBytes(4); fsb5Header.version = (int)fr32(br); fsb5Header.numSamples = (int)fr32(br); fsb5Header.shdrSize = (int)fr32(br); fsb5Header.nameSize = (int)fr32(br); fsb5Header.dataSize = (int)fr32(br); fsb5Header.mode = fr32(br); if (fsb5Header.version == 0) fsb5Header.extra = br.ReadBytes(4); else fsb5Header.extra = null; fsb5Header.zero = br.ReadBytes(8); fsb5Header.hash = br.ReadBytes(16); fsb5Header.dummy = br.ReadBytes(8); return (int)(br.BaseStream.Position - oldPosition); }
static void Main(string[] args) { if (args.Length < 1) { Console.WriteLine("Syntax: {0} <fsb file> [<output directory>]", typeof(Program).Assembly.Location); Console.WriteLine("NOTE: If the output directory is not given, the new FSB files will be written"); Console.WriteLine(" to the same directory as the given FSB file."); return; } string outputDirectory; if (args.Length == 2) outputDirectory = args[1]; else { outputDirectory = Path.ChangeExtension(args[0], ""); outputDirectory = outputDirectory.Remove(outputDirectory.Length - 1); } if (!Directory.Exists(outputDirectory)) Directory.CreateDirectory(outputDirectory); using (var br = new BinaryReader(File.OpenRead(args[0]))) { int sign = CheckSignEndian(br); if (sign < 0) { Console.WriteLine("Invalid file detected."); return; } if (sign != '5') { Console.WriteLine("This tool is designed for FSB5 files only."); return; } var fsb5Header = new FSB5Header(); int fhSize = ReadFSB5Header(br, fsb5Header); uint nameOffset = (uint)(fhSize + fsb5Header.shdrSize); uint fileOffset = (uint)(nameOffset + fsb5Header.nameSize); uint baseOffset = fileOffset; bool pcmEndian = (fsb5Header.zero[4] & 1) == 1; for (int i = 0; i < fsb5Header.numSamples; ++i) { uint offset = fr32(br); uint type = offset & 0x7F; var shdrData = BitConverter.GetBytes(type); shdrData = shdrData.Concat(br.ReadBytes(4)).ToArray(); offset = GET_FSB5_OFFSET(offset); // This is the offset into the file section long currOffset; while ((type & 1) == 1) { uint t32 = fr32(br); shdrData = shdrData.Concat(BitConverter.GetBytes(t32)).ToArray(); type = t32 & 1; int len = (int)((t32 & 0xFFFFFF) >> 1); t32 >>= 24; currOffset = br.BaseStream.Position; shdrData = shdrData.Concat(br.ReadBytes(len)).ToArray(); currOffset += len; br.BaseStream.Position = currOffset; } currOffset = br.BaseStream.Position; uint size; if (br.BaseStream.Position < nameOffset) { size = fr32(br); if (size == 0) size = (uint)br.BaseStream.Length; else size = GET_FSB5_OFFSET(size) + baseOffset; } else size = (uint)br.BaseStream.Length; br.BaseStream.Position = currOffset; fileOffset = baseOffset + offset; size -= fileOffset; string name = ""; if (fsb5Header.nameSize != 0) { currOffset = br.BaseStream.Position; br.BaseStream.Position = nameOffset + i * 4; br.BaseStream.Position = nameOffset + fr32(br); do { byte c = br.ReadByte(); if (c == 0) break; name += (char)c; } while (true); br.BaseStream.Position = currOffset; } Console.Write("Processing {0}...", name); string outputFilename = Path.Combine(outputDirectory, name + ".fsb"); currOffset = br.BaseStream.Position; br.BaseStream.Position = fileOffset; // Get file using (var bw = new BinaryWriter(File.Create(outputFilename))) { bw.Write(Encoding.ASCII.GetBytes("FSB5")); bw.Write(fsb5Header.version); bw.Write(1); bw.Write(shdrData.Length); int fullNameSize = (int)Math.Ceiling((name.Length + 5) / 16.0) * 16; bw.Write(fullNameSize); bw.Write(size); bw.Write(fsb5Header.mode); if (fsb5Header.version == 0) bw.Write(fsb5Header.extra); bw.Write(fsb5Header.zero); bw.Write(fsb5Header.hash); bw.Write(fsb5Header.dummy); bw.Write(shdrData); bw.Write(4); bw.Write(Encoding.ASCII.GetBytes(name)); bw.Write((byte)0); for (int j = name.Length + 5; j < fullNameSize; ++j) bw.Write((byte)0); br.BaseStream.CopyStream(bw.BaseStream, (int)size); } fileOffset += size; br.BaseStream.Position = currOffset; #if FMOD // Also use the FMOD API to output to a WAV file FMOD.System system; var result = FMOD.Factory.System_Create(out system); string outputWAV = Path.Combine(outputDirectory, name + ".wav"); result = system.setOutput(FMOD.OUTPUTTYPE.WAVWRITER); result = system.init(32, FMOD.INITFLAGS.NORMAL, System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(outputWAV)); FMOD.Sound sound; result = system.createSound(args[0], FMOD.MODE._2D | FMOD.MODE.LOOP_OFF | FMOD.MODE.CREATESTREAM, out sound); FMOD.Sound subsound; result = sound.getSubSound(i, out subsound); FMOD.Channel channel; result = system.playSound(subsound, null, false, out channel); do { result = system.update(); if (channel != null) { bool playing; result = channel.isPlaying(out playing); if (!playing) break; } } while (true); result = sound.release(); result = system.close(); result = system.release(); #endif Console.WriteLine(" DONE!"); } } }