Ask Question

Name:
Title:
Your Question:

Answer Question

Name:
Your Answer:
User Submitted Source Code!


Description:
  file
Language: C/C++
Code:
// extracts ATRAC9 audio files from FMOD sound bank (FSB5) files for Bloodborne
//by adlem/albeartron
/*
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation, either version 3 of the License, or
     (at your option) any later version.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <iostream>
#include <fstream>
#include <inttypes.h>
#include <string>

#define VERSION "Rev0-1"

#pragma pack(1)     //bytewise
struct ATRAC9_HEADER {
     //used to assemble the header that goes on the output data
     //ATRAC9 header info from PS4 SDK documentation
     unsigned char     RIFF_Chunk_ID[4] = { 0x52, 0x49, 0x46, 0x46 };     //alwyas "RIFF"
     std::uint32_t     RIFF_Chunk_Data_Size;          //should be 92 + datasize (0x5C + datasize)
     unsigned char   RIFF_Type_ID[4] = { 0x57, 0x41, 0x56, 0x45 };     //always 'WAVE"
     //fmt chunk
     unsigned char   fmt_Chunk_ID[4] = { 0x66, 0x6d, 0x74, 0x20 };     //always "fmt "
     std::uint32_t   fmt_Chunk_Data_Size = 52;     //size of the fmt chunk is always 52 (0x34)
     std::uint16_t   wFormatTag = 0xFFFE;          //WAVE_FORMAT_EXTENSIBLE = 0xFFFE (a.k.a. AT9)
     std::uint16_t   nChannels;                         //determined by FSB header
     std::uint32_t   nSamplesPerSec;                    //determined by FSB header
     std::uint32_t   nAvgBytesPerSec;               //Average byte rate [Byte/s], Calculated by rounding off nBlockAlign * nSamplesPerSec / wSamplesPerBlock
     std::uint16_t     nBlockAlign;                    //Audio block size [Byte]
     std::uint16_t   wBitsPerSample = 0;               //Quantifying bit number (0)
     std::uint16_t   cbSize = 34;                    //Extension size (34)
     std::uint16_t   wSamplesPerBlock = 1024;     //Number of output samples of the audio block
     std::uint32_t   dwChannelMask = 4;               //Channel map of speaker position
     std::uint8_t    SubFormat[16] = { 0xD2, 0x42, 0xE1, 0x47, 0xBA, 0x36, 0x8D, 0x4D, 0x88, 0xFC, 0x61, 0x65, 0x4F, 0x8C, 0x83, 0x6C };     //GUID,  Codec identifier (KSDATAFORMAT_SUBTYPE_ATRAC9 = {47E142D2-36BA-4d8d-88FC-61654F8C836C})
     std::uint32_t   dwVersionInfo = 1;               //Version information (1 or 2)
     //ATRAC9 config data bitfield
     unsigned char   atrac9_id : 8;
     std::uint8_t    reserved1 : 1;
     std::uint8_t    sampling_rate_index : 3;
     std::uint8_t    channel_config_index : 3;
     std::uint8_t    reserved2 : 1;
     std::uint16_t   frame_length_index : 11;
     std::uint8_t    super_frame_index : 2;
     std::uint8_t    reserved3 : 3;
     std::uint32_t     reserved4 = 0;     //padding
     //fact chunk
     unsigned char   fact_Chunk_ID[4] = { 0x66, 0x61 ,0x63, 0x74 };     //always "fact"
     std::uint32_t   fact_Chunk_Data_Size = 12;     //size of the fact chunk is always 12
     std::uint32_t   Total_Samples;                    //found in FSB sample header
     std::uint32_t   Input_And_Overlap_Delay_Samples = 256;     
     std::uint32_t   Encoder_Delay_Samples = 256;               
     //data chunk
     unsigned char   data_Chunk_ID[4] = { 0x64, 0x61, 0x74, 0x61 };     //always "data"
     std::uint32_t   data_Chunk_Data_Size;
};

struct FSB5_HEADER {
     //this info found in "FSB5.bt" template by Simon Pinfold for "010 Editor"
     unsigned char     FSB5_ID[4];               //FSB5 identifier
     std::uint32_t     FSB5_version;          //sub version number, e.g. 5.1
     std::uint32_t     numSamples;               //number of file/sample entries in this FSB
     std::uint32_t     sampleHeaderSize;     //size of the headers info section (following this main header)
     std::uint32_t     nameTableSize;          //size of the names table section (following the headers section)
     std::uint32_t     dataSize;               //size of the data section (following the names table section)
     //settings
     std::uint32_t     mode;                    //at9 files are 0x0000000D = 13; not included in the original FSB5.bt template
     std::uint8_t    unknown01[8];          //unknown
     std::uint8_t    unknown02[16];          //unknown hash?
     std::uint8_t    unknown03[8];          //unknown
     //headers table follows this section
};

struct FSB5_SAMPLE_HEADER {
     //just the sample header:
     std::uint32_t     extraParams : 1;     //whether or not there are exta chunks after this info
     std::uint32_t     frequency : 4;
     std::uint32_t     twoChannels : 1;
     std::uint32_t     dataOffset : 28;
     std::uint32_t     samples : 30;
     //54 bits... 6 bytes
};

struct FSB5_SAMPLE_CHUNK_DEF {
     //fsb5 has different chunks in each sample header, each begins with this structure
     std::uint32_t     next : 1;     //more chunks to follow?
     std::uint32_t     size : 24;     //size of this chunk (just the data)
     std::uint32_t     chunkType : 7;     //chunk type 3=loop, 9=ATRAC9, etc. only caring about ATRAC9 for this
};

struct FSB5_SAMPLE_ATRAC9_CHUNK {
     //chunk type 9
     std::uint32_t     unknownByte;     //there's a byte at the beginning of this chunk that might be important but is currently unknown, probably a bitfield that has the info for blockAlign/other things
     std::uint8_t     atrac9_id : 8;                    //ATRAC9™ format data(0xFE)
     std::uint8_t    reserved1 : 1;                    //Reserved area
     std::uint8_t    sampling_rate_index : 3;     //Sampling rate and number of samples per frame
     std::uint8_t    channel_config_index : 3;     //Number of channels and audio blocks
     std::uint8_t    reserved2 : 1;                    //Reserved area
     std::uint16_t   frame_length_index : 11;     //Frame length for bitstreaming, The actual frame length will be (frame_length_index + 1) bytes.
     std::uint8_t    super_frame_index : 2;          //Number of frames in a superframe
     std::uint8_t    reserved3 : 3;                    //Reserved area
};
#pragma pack()

static void show_usage(std::string);

int main(int argc, char *argv[]) {
     if (argc < 2) {
          show_usage(argv[0]);
          return 1;
     }

     char* filenametemp = argv[1];
     std::ifstream inputFile(argv[1], std::ios::binary);          //open file as binary

     ATRAC9_HEADER outputHeader;     //the at9 output file's header, to be populated by this program

     //header/chunk structs:
     FSB5_HEADER     fsbHeader;
     FSB5_SAMPLE_HEADER fsbSampleHeader, nextSampleHeader;     //each file's header info
     FSB5_SAMPLE_CHUNK_DEF fsbSampleChunk;     //if extraParams != 0
     FSB5_SAMPLE_ATRAC9_CHUNK fsbAt9Chunk;     //important chunk that will dictate ATRAC9 header info

     //variables
     std::uint32_t     sampleTableStart;     //where the samples data starts
     std::uint32_t     nameTableStart;          //where the name table starts (the nametable offset list)
     std::uint32_t     dataTableStart;          //where the actual data table starts
     std::uint32_t     dataTableEnd;          //where the end of the data table is (EOF)

     std::uint32_t     returnAddress;     //where to come back to after getting name + data
     std::uint32_t     sampleTableCurrent;     //the current working location in the sample table
     std::uint32_t     nameTableCurrent;     //the current working location in the name table (offsets list)
     std::uint32_t     nameTableTextOffset;     //start + offset = where the text is
     std::uint32_t     dataTableCurrent;     //the current working location in the data table
     std::uint32_t     nextDataOffset;     //used to know where to stop reading
     std::uint32_t     dataEntrySize;

     const int MAX_BUFFSIZE = 512;
     char stringBuffer[MAX_BUFFSIZE];

     unsigned char tempChar = 0xFF;
     std::uint8_t     tempByte;
     std::uint8_t     tempChannels;     //number of channels in the output
     std::string          tempString;     //use for filename output


     //read FSB5 main header (done once)
     inputFile.read((char *)&fsbHeader, sizeof(fsbHeader));
     
     std::printf("FSB5 ID\t%.*s\n", sizeof(fsbHeader.FSB5_ID), fsbHeader.FSB5_ID);     //string of char [4] (print size/ptr because non null terminated)
     std::printf("Sub Version\t0x%08X\t%d\n", fsbHeader.FSB5_version, fsbHeader.FSB5_version);
     std::printf("numSamples\t0x%08X\t%d\n", fsbHeader.numSamples, fsbHeader.numSamples);
     sampleTableStart = 0x3C;     //not actually important
     std::printf("sampleHeaderSize\t0x%08X\t%d\n", fsbHeader.sampleHeaderSize, fsbHeader.sampleHeaderSize);
     nameTableStart = sampleTableStart + fsbHeader.sampleHeaderSize;     //name table starts after FSBheader + sampleHeader
     nameTableCurrent = nameTableStart;     //initial position in name table
     std::printf("nameTableSize\t0x%08X\t%d\n", fsbHeader.nameTableSize, fsbHeader.nameTableSize);
     dataTableStart = nameTableStart + fsbHeader.nameTableSize;
     dataTableCurrent = dataTableStart;     //initial position in the data table
     std::printf("dataSize\t0x%08X\t%d\n", fsbHeader.dataSize, fsbHeader.dataSize);
     dataTableEnd = dataTableStart + fsbHeader.dataSize;     //should probably verify that this = EOF
     std::printf("mode\t0x%08X\t%d\n", fsbHeader.mode, fsbHeader.mode);     //it's AT9 mode for the files we care about
     std::printf("unknown01\t0x");
     for (std::uint8_t i = 0; i < sizeof(fsbHeader.unknown01); i++) { std::printf("%02X", fsbHeader.unknown01[i]); } std::printf("\n");
     std::printf("unknown02\t0x");
     for (std::uint8_t i = 0; i < sizeof(fsbHeader.unknown02); i++) { std::printf("%02X", fsbHeader.unknown02[i]); } std::printf("\n");
     std::printf("unknown03\t0x");
     for (std::uint8_t i = 0; i < sizeof(fsbHeader.unknown03); i++) { std::printf("%02X", fsbHeader.unknown03[i]); } std::printf("\n");
     std::printf("\n");

     std::printf(
          "no." "\t"
          "xtra" "\t"
          "freq" "\t"
          "2ch?" "\t"
          "datoffset" "\t"
          "#samples" "\t"
          "filename" "\t"
          "dataBegin" "\t"
          "dataEnd" "\n"
     );


     for (size_t sampleNumber = 0; sampleNumber < fsbHeader.numSamples; sampleNumber++)
     //for (size_t sampleNumber = 0; sampleNumber < 1; sampleNumber++)     //one once for debugging
     {
          std::printf("%d\t", sampleNumber);
          //read SAMPLE header (done for each sample, numsamples times)
          inputFile.read((char *)&fsbSampleHeader, sizeof(fsbSampleHeader));     //read sample header
          //show it
          std::printf("%d\t", fsbSampleHeader.extraParams);
          std::printf("%d\t", fsbSampleHeader.frequency);
          std::printf("%d\t", fsbSampleHeader.twoChannels);
          tempChannels = fsbSampleHeader.twoChannels + 1;     //0+1=1, 1+1=2; may be overwritten by CHANNELS chunk info later
          std::printf("0x%08X\t", fsbSampleHeader.dataOffset);     //this is dataStart + this offset
          dataTableCurrent = dataTableStart + (fsbSampleHeader.dataOffset * 16);
          std::printf("%d\t", fsbSampleHeader.samples);

          //read SAMPLE header's extra params (if there are any)
          if (fsbSampleHeader.extraParams) {
               do
               {
                    //read the SAMPLE header's extra param chunk info
                    inputFile.read((char *)&fsbSampleChunk, sizeof(fsbSampleChunk));
                    switch (fsbSampleChunk.chunkType)     //do something based on the chunk type
                    {
                    case 1:     //channels chunk
                         inputFile.read((char *)&tempChannels, sizeof(tempChannels));     //if there's a channels chunk, read it and store the #
                         //only used for files with >2 channels
                         break;
                    case 3:     //loop chunk
                         inputFile.seekg(fsbSampleChunk.size, std::ios::cur);     //skip for now
                         //could add loop info to the ATRAC9 header
                         break;
                    case 9: //ATRAC9 chunk
                         inputFile.read((char *)&fsbAt9Chunk, sizeof(fsbAt9Chunk));     //read the ATRAC9 config data
                         break;
                    default:
                         inputFile.seekg(fsbSampleChunk.size, std::ios::cur);     //skip this unknown chunk
                         //several other chunk types were encountered in the Bloodborne files but no time was spent to learn what they do as they don't seem important for decoding
                         break;
                    }
               } while (fsbSampleChunk.next);     //if next = 1, repeat the above since there are more chunks for this sample
          }
          sampleTableCurrent = inputFile.tellg();     //remember where we were in the sampleHeaders, last step for this file will be returning here
          //go to the name table (first time this is at nameTableStart)
          inputFile.seekg(nameTableCurrent);
          //determine the name location, save in fileNameOffset
          inputFile.read((char *)&nameTableTextOffset, sizeof(nameTableTextOffset));     //read the offset from the nametable offset list
          nameTableCurrent = inputFile.tellg();     //remember where the next name offset is for next time
          inputFile.seekg(nameTableTextOffset + nameTableStart);     //go to the actual filename location, which is at nameTableStart + whatever offset
          //read until null encountered. this is a dumb way to do this but it worked...
          tempString.clear();
          while (tempChar != 0x00)
          {
               inputFile.read((char *)&tempChar, sizeof(tempChar));
               if (tempChar == 0x00)
               {
                    break;     //don't add the null
               }
               tempString += tempChar;
          }
          tempString += ".at9\0";
          tempChar = 0xFF;

          std::printf("\t");
          std::printf("%s\t", tempString.c_str());     //this should be the filename

          std::ofstream outputFile(tempString, std::ios::binary);          //create the output file and open file as binary
     
          //doesn't have data chunk size info so:
          //check if this is the last entry
          if (sampleNumber + 1 == fsbHeader.numSamples)
          {
               //this is the last sample, read until EOF
               dataEntrySize = dataTableEnd - dataTableCurrent;     //size of entry is EOF - currentoffset
          }
          else
          {
               //not the last sample, so get next sampleheader to find where the next sample begins
               inputFile.seekg(sampleTableCurrent);
               inputFile.read((char *)&nextSampleHeader, sizeof(nextSampleHeader));
               dataEntrySize = (dataTableStart + (nextSampleHeader.dataOffset * 16)) - dataTableCurrent;     //size of this entry is nextoffset - currentoffset
          }

          //write the ATRAC9 config data to the at9 output's header here
          //riff
          outputHeader.RIFF_Chunk_Data_Size = 0x58 + dataEntrySize;     //ATRAC9 could have extra chunks but this program doesn't add them in
          outputHeader.nChannels = tempChannels;
          outputHeader.nSamplesPerSec = 48000;     //need to add code to interpret fsbSampleHeader.frequency, but '9' = 48000Hz and all the files appear the same for Bloodborne
          outputHeader.nAvgBytesPerSec = 9000;     //should calculate this instead, but at9tool will still extract audio with incorrect value
          outputHeader.nBlockAlign = 48;     //should calculate this instead, but at9tool will still extract audio with incorrect value (may be in FSB ATRAC9 chunk's first half)
          //config
          outputHeader.atrac9_id = fsbAt9Chunk.atrac9_id;
          outputHeader.reserved1 = fsbAt9Chunk.reserved1;
          outputHeader.sampling_rate_index = fsbAt9Chunk.sampling_rate_index;
          outputHeader.channel_config_index = fsbAt9Chunk.channel_config_index;
          outputHeader.reserved2 = fsbAt9Chunk.reserved2;
          outputHeader.frame_length_index = fsbAt9Chunk.frame_length_index;
          outputHeader.super_frame_index = fsbAt9Chunk.super_frame_index;
          outputHeader.reserved3 = fsbAt9Chunk.reserved3;
          //fact
          outputHeader.Total_Samples = fsbSampleHeader.samples;
          outputHeader.data_Chunk_Data_Size = dataEntrySize;

          outputFile.write((char *)&outputHeader, sizeof(outputHeader));     //write the ATRAC9 header

          inputFile.seekg(dataTableCurrent);     //go to where the data starts
          //read data starting here, total size of dataEntrySize

          std::printf("0x%08X\t", dataTableCurrent);
          std::printf("0x%08X\t", dataEntrySize);
          std::printf("\n");

          //send the data from the FSB into the new file
          //there's probably a better way to do this, but this seems to work OK
          for (size_t i = 0; i < dataEntrySize; i++)
          {
               inputFile.read((char *)&tempByte, sizeof(tempByte));
               outputFile.write((char *)&tempByte, sizeof(tempByte));
          }

          outputFile.close();
          //then move onto the next sample and repeat the process numSamples times total 
          inputFile.seekg(sampleTableCurrent);
     }
     
     
     return 0;
}

static void show_usage(std::string execName) {
     std::cerr
          << "Revision: " << VERSION << '\n'
          << "Usage: " << execName << " <FILE1>\n"
          << "\t<FILE1>\tFSB5 input file (required)\n";
}
Comments: