#include "Common/Log.h" #include "Common/StringUtils.h" #include "Core/Util/AtracTrack.h" #include "Core/HLE/ErrorCodes.h" #include "Common/Data/Format/RIFF.h" #include "Core/MemMap.h" // Atrac file parsing constants constexpr int RIFF_CHUNK_MAGIC = 0x46464952; constexpr int RIFF_WAVE_MAGIC = 0x45564157; constexpr int FMT_CHUNK_MAGIC = 0x20746D66; constexpr int DATA_CHUNK_MAGIC = 0x61746164; constexpr int SMPL_CHUNK_MAGIC = 0x6C706D73; constexpr int FACT_CHUNK_MAGIC = 0x74636166; constexpr u32 WAVE_FORMAT_EXTENSIBLE = 0xFFFE; constexpr u32 WAVE_FORMAT_AT3 = 0x270; static u16 Read16(const u8 *buffer, int offset) { u16 value; memcpy(&value, buffer + offset, sizeof(u16)); return value; } static u32 Read32(const u8 *buffer, int offset) { u32 value; memcpy(&value, buffer + offset, sizeof(u32)); return value; } // Old WAVE parser. int AnalyzeAtracTrack(const u8 *buffer, u32 size, Track *track, std::string *error) { // 72 is about the size of the minimum required data to even be valid. if (size < 72) { return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; } // If the pointer is bad, let's try to survive, although I'm pretty sure that on a real PSP, // we crash here. if (!buffer) { return SCE_KERNEL_ERROR_INVALID_POINTER; } // TODO: Validate stuff more. if (Read32(buffer, 0) != RIFF_CHUNK_MAGIC) { ERROR_LOG(Log::ME, "Couldn't find RIFF header"); return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } struct RIFFFmtChunk { u16 fmtTag; u16 channels; u32 samplerate; u32 avgBytesPerSec; u16 blockAlign; }; u32 offset = 8; track->firstSampleOffset = 0; while (Read32(buffer, offset) != RIFF_WAVE_MAGIC) { // Get the size preceding the magic. int chunk = Read32(buffer, offset - 4); // Round the chunk size up to the nearest 2. offset += chunk + (chunk & 1); if (offset + 12 > size) { *error = StringFromFormat("%d too small for WAVE chunk at offset %d", size, offset); return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; } if (Read32(buffer, offset) != RIFF_CHUNK_MAGIC) { *error = "RIFF chunk did not contain WAVE"; return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } offset += 8; } offset += 4; if (offset != 12) { WARN_LOG(Log::ME, "RIFF chunk at offset: %d", offset); } // RIFF size excluding chunk header. track->fileSize = Read32(buffer, offset - 8) + 8; // Even if the RIFF size is too low, it may simply be incorrect. This works on real firmware. u32 maxSize = std::max(track->fileSize, size); bool bfoundData = false; u32 dataChunkSize = 0; int sampleOffsetAdjust = 0; while (maxSize >= offset + 8 && !bfoundData) { int chunkMagic = Read32(buffer, offset); u32 chunkSize = Read32(buffer, offset + 4); // Account for odd sized chunks. if (chunkSize & 1) { WARN_LOG(Log::ME, "RIFF chunk had uneven size"); } chunkSize += (chunkSize & 1); offset += 8; if (chunkSize > maxSize - offset) break; switch (chunkMagic) { case FMT_CHUNK_MAGIC: { if (track->codecType != 0) { *error = "AnalyzeTrack: multiple fmt chunks is not valid"; return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } auto at3fmt = (const RIFFFmtChunk *)(buffer + offset); if (chunkSize < 32 || (at3fmt->fmtTag == WAVE_FORMAT_EXTENSIBLE && chunkSize < 52)) { *error = "AnalyzeTrack: fmt definition too small(%d)"; return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } if (at3fmt->fmtTag == WAVE_FORMAT_AT3) track->codecType = PSP_CODEC_AT3; else if (at3fmt->fmtTag == WAVE_FORMAT_EXTENSIBLE) track->codecType = PSP_CODEC_AT3PLUS; else { *error = "AnalyzeTrack: invalid fmt magic: %04x"; return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } track->channels = at3fmt->channels; if (track->channels != 1 && track->channels != 2) { *error = "AnalyzeTrack: unsupported channel count %d"; return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } if (at3fmt->samplerate != 44100) { *error = "AnalyzeTrack: unsupported sample rate %d"; return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } track->bitrate = at3fmt->avgBytesPerSec * 8; track->bytesPerFrame = at3fmt->blockAlign; if (track->bytesPerFrame == 0) { *error = "invalid bytes per frame: %d"; return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } // TODO: There are some format specific bytes here which seem to have fixed values? // Probably don't need them. if (at3fmt->fmtTag == WAVE_FORMAT_AT3) { // 0x270 // This is the offset to the jointStereo_ field. track->jointStereo = Read16(buffer, offset + 24); // Then there are more fields here. u16 unknown1_2 = Read16(buffer, offset + 30); } else if (at3fmt->fmtTag == WAVE_FORMAT_EXTENSIBLE) { // It's in an "Extensible" wave format. Let's read some more. } if (chunkSize > 16) { // Read and format extra bytes as hexadecimal std::string hex; DataToHexString(buffer + offset + 16, chunkSize - 16, &hex, false); DEBUG_LOG(Log::ME, "Additional chunk data (beyond 16 bytes): %s", hex.c_str()); } break; } case FACT_CHUNK_MAGIC: { track->endSample = Read32(buffer, offset); if (chunkSize >= 8) { track->firstSampleOffset = Read32(buffer, offset + 4); } if (chunkSize >= 12) { u32 largerOffset = Read32(buffer, offset + 8); // Works, but "largerOffset"?? sampleOffsetAdjust = track->firstSampleOffset - largerOffset; } break; } case SMPL_CHUNK_MAGIC: { if (chunkSize < 32) { *error = StringFromFormat("smpl chunk too small (%d)", chunkSize); return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } int checkNumLoops = Read32(buffer, offset + 28); if (checkNumLoops != 0 && chunkSize < 36 + 20) { *error = StringFromFormat("smpl chunk too small for loop (%d, %d)", checkNumLoops, chunkSize); return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } if (checkNumLoops < 0) { *error = StringFromFormat("bad checkNumLoops (%d)", checkNumLoops); return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } track->loopinfo.resize(checkNumLoops); u32 loopinfoOffset = offset + 36; // The PSP only cares about the first loop start and end, it seems. // Most likely can skip the rest of this data, but it's not hurting anyone. for (int i = 0; i < checkNumLoops && 36 + (u32)i < chunkSize; i++, loopinfoOffset += 24) { track->loopinfo[i].cuePointID = Read32(buffer, loopinfoOffset + 0); track->loopinfo[i].type = Read32(buffer, loopinfoOffset + 4); track->loopinfo[i].startSample = Read32(buffer, loopinfoOffset + 8); track->loopinfo[i].endSample = Read32(buffer, loopinfoOffset + 12); track->loopinfo[i].fraction = Read32(buffer, loopinfoOffset + 16); track->loopinfo[i].playCount = Read32(buffer, loopinfoOffset + 20); if (i == 0 && track->loopinfo[i].startSample >= track->loopinfo[i].endSample) { *error = "AnalyzeTrack: loop starts after it ends"; return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS; } } break; } case DATA_CHUNK_MAGIC: { bfoundData = true; track->dataByteOffset = offset; dataChunkSize = chunkSize; if (track->fileSize < offset + chunkSize) { WARN_LOG(Log::ME, "Atrac data chunk extends beyond riff chunk"); track->fileSize = offset + chunkSize; } } break; } offset += chunkSize; } if (track->codecType == 0) { *error = "Could not detect codec"; return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } if (!bfoundData) { *error = "AnalyzeTrack: No data chunk found"; return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; } // set the loopStartSample_ and loopEndSample_ by loopinfo_ if (track->loopinfo.size() > 0) { track->loopStartSample = track->loopinfo[0].startSample + track->FirstOffsetExtra() + sampleOffsetAdjust; track->loopEndSample = track->loopinfo[0].endSample + track->FirstOffsetExtra() + sampleOffsetAdjust; } else { track->loopStartSample = -1; track->loopEndSample = -1; } // if there is no correct endsample, try to guess it if (track->endSample <= 0 && track->bytesPerFrame != 0) { track->endSample = (dataChunkSize / track->bytesPerFrame) * track->SamplesPerFrame(); track->endSample -= track->FirstSampleOffsetFull(); } track->endSample -= 1; if (track->loopEndSample != -1 && track->loopEndSample > track->endSample + track->FirstSampleOffsetFull()) { *error = "AnalyzeTrack: loop after end of data"; return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS; } return 0; } int AnalyzeAA3Track(const u8 *buffer, u32 size, u32 fileSize, Track *track, std::string *error) { // TODO: Make sure this validation is correct, more testing. if (size < 10) { return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL; } // If the pointer is bad, let's try to survive, although I'm pretty sure that on a real PSP, // we crash here. if (!buffer) { _dbg_assert_(false); return SCE_KERNEL_ERROR_INVALID_POINTER; } if (buffer[0] != 'e' || buffer[1] != 'a' || buffer[2] != '3') { return SCE_ERROR_ATRAC_AA3_INVALID_DATA; } // It starts with an id3 header (replaced with ea3.) This is the size. u32 tagSize = buffer[9] | (buffer[8] << 7) | (buffer[7] << 14) | (buffer[6] << 21); if (size < tagSize + 36) { return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL; } // EA3 header starts at id3 header (10) + tagSize. buffer = buffer + 10 + tagSize; if (buffer[0] != 'E' || buffer[1] != 'A' || buffer[2] != '3') { ERROR_LOG(Log::ME, "AnalyzeAA3Track: Invalid EA3 magic bytes"); return SCE_ERROR_ATRAC_AA3_INVALID_DATA; } track->fileSize = fileSize; // Based on FFmpeg's code. u32 codecParams = buffer[33] | (buffer[34] << 8) | (buffer[35] << 16); const u32 at3SampleRates[8] = { 32000, 44100, 48000, 88200, 96000, 0 }; switch (buffer[32]) { case 0: track->codecType = PSP_CODEC_AT3; track->bytesPerFrame = (codecParams & 0x03FF) * 8; track->bitrate = at3SampleRates[(codecParams >> 13) & 7] * track->bytesPerFrame * 8 / 1024; track->channels = 2; track->jointStereo = (codecParams >> 17) & 1; break; case 1: track->codecType = PSP_CODEC_AT3PLUS; track->bytesPerFrame = ((codecParams & 0x03FF) * 8) + 8; track->bitrate = at3SampleRates[(codecParams >> 13) & 7] * track->bytesPerFrame * 8 / 2048; track->channels = (codecParams >> 10) & 7; break; case 3: case 4: case 5: ERROR_LOG(Log::ME, "AnalyzeAA3Track: unsupported codec type %d", buffer[32]); return SCE_ERROR_ATRAC_AA3_INVALID_DATA; default: ERROR_LOG(Log::ME, "AnalyzeAA3Track: invalid codec type %d", buffer[32]); return SCE_ERROR_ATRAC_AA3_INVALID_DATA; } track->dataByteOffset = 10 + tagSize + 96; track->firstSampleOffset = 0; if (track->endSample < 0 && track->bytesPerFrame != 0) { track->endSample = ((track->fileSize - track->dataByteOffset) / track->bytesPerFrame) * track->SamplesPerFrame(); } track->endSample -= 1; return 0; } static inline u32 Read16(const u8 *base, int *offset) { // Little-endian reliance. u32 value = 0; memcpy(&value, base + *offset, 2); *offset += 2; return value; } static inline u32 Read32(const u8 *base, int *offset) { // Little-endian reliance. u32 value = 0; memcpy(&value, base + *offset, 4); *offset += 4; return value; } static const u8 g_atrac3Checkbytes[] = { 0xBF, 0xAA, 0x23, 0xE9, 0x58, 0xCB, 0x71, 0x44, 0xA1, 0x19, 0xFF, 0xFA, 0x01, 0xE4, 0xCE, 0x62, }; static inline int RoundUpToEven(int size) { // Round up to the next even number. return (size + 1) & ~1; } int ParseWaveAT3(const u8 *data, u32 dataLength, TrackInfo *track) { _assert_(data != nullptr); track->loopStart = 0xFFFFFFFF; track->loopEnd = 0xFFFFFFFF; track->firstSampleOffset = 0; track->endSample = 0; track->waveDataSize = 0; int retval = SCE_ERROR_ATRAC_UNKNOWN_FORMAT; int offset = 0; // u8 offset into the data array. Is kept even. // Scan RIFF chunks for the RIFFWAVE header. Normally we find this immediately. while (true) { if (offset + 0xC >= dataLength) { return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; } const u32 blockID = Read32(data, &offset); if (blockID != RIFF_CHUNK_MAGIC) { return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } const u32 blockSize = RoundUpToEven(Read32(data, &offset)); const u32 waveID = Read32(data, &offset); if (waveID == RIFF_WAVE_MAGIC) { // We found the WAVE header. break; } offset += blockSize - 4; } // Now that we have the header, loop through the rest of the chunks. bool modifiedSampleOffset = false; while (true) { if (offset + 8 >= dataLength) { return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; } const u32 chunkID = Read32(data, &offset); const int chunkSize = RoundUpToEven(Read32(data, &offset)); const int nextOffset = offset + chunkSize; // We allow the data chunk to be bigger than readSize, as we may not have read the whole file. if (offset + chunkSize > dataLength && chunkID != DATA_CHUNK_MAGIC) { return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; } int remainingBytes = 0; switch (chunkID) { case DATA_CHUNK_MAGIC: { // This should be the last chunk we find, it lasts to the end of the file. // I guess another order is possible, but not if streaming. track->waveDataSize = chunkSize; track->dataOff = offset; if (!track->firstSampleOffset) { INFO_LOG(Log::Atrac, "DATA chunk found at offset %d with size %d", offset, chunkSize); track->firstSampleOffset = retval == PSP_CODEC_AT3 ? 0x400 : 0x800; } if (modifiedSampleOffset && retval == PSP_CODEC_AT3PLUS) { track->firstSampleOffset -= 0xb8; if (track->loopEnd != 0xFFFFFFFF) { track->loopEnd -= 0xb8; track->loopStart -= 0xb8; } } return retval; } case FMT_CHUNK_MAGIC: { if (retval != SCE_ERROR_ATRAC_UNKNOWN_FORMAT) { // This means dual FMT chunks, which is not valid. return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } if (chunkSize < 0x20) { return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } const u16 fmtTag = Read16(data, &offset); const u16 chans = Read16(data, &offset); track->numChans = chans; if (chans != 1 && chans != 2) { // Only stereo and mono are supported. return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } const int sampleRate = Read32(data, &offset); if (sampleRate != 44100) { // Only a single sample rate is supported. return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } offset += 4; // skip avgBytesPerSec track->blockAlign = (u16)Read16(data, &offset); if (track->blockAlign == 0) { return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } if (fmtTag == WAVE_FORMAT_AT3) { // Atrac3 format. offset += 4; // skip the extra bytes const u16 jointStereo = Read16(data, &offset); if (jointStereo != 1) { return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } offset += 4; const u16 sampleSizeTailFlag = Read16(data, &offset); track->sampleSizeMaybe = (u8)sampleSizeTailFlag; track->tailFlag = (u8)(sampleSizeTailFlag >> 8); const u16 unknown2 = Read16(data, &offset); if ((sampleSizeTailFlag & 0xffff) != unknown2) { return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } // Don't what this is, but it seems to be always 1. const u32 supposedToBeOne = Read32(data, &offset); if (supposedToBeOne != 1) { return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } retval = PSP_CODEC_AT3; // ATRAC3 remainingBytes = chunkSize - 0x20; } else if (fmtTag == WAVE_FORMAT_EXTENSIBLE) { // Atrac3+ format, probably. if (chunkSize < 0x34) { return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } if (memcmp(data + offset + 10, g_atrac3Checkbytes, sizeof(g_atrac3Checkbytes)) != 0) { return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } track->sampleSizeMaybe = data[offset + 0x1c]; track->tailFlag = data[offset + 0x1d]; if ((track->sampleSizeMaybe << 27) >> 29 != track->numChans) { return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } offset += 0x26; // Or, use the blocksize? retval = PSP_CODEC_AT3PLUS; remainingBytes = chunkSize - 0x34; } else { // Unsupported fmtTag. return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } if (remainingBytes) { INFO_LOG(Log::Atrac, "FMT chunk has %d extra bytes", remainingBytes); } break; } case SMPL_CHUNK_MAGIC: { if ((int)track->loopStart < 0) { if (chunkSize < 0x20) { return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } offset += 0x1c; const int numLoops = Read32(data, &offset); if (numLoops != 0) { if (chunkSize < 0x34) { return SCE_ERROR_ATRAC_SIZE_TOO_SMALL; } offset += 0xc; track->loopStart = Read32(data, &offset); track->loopEnd = Read32(data, &offset); if (track->loopEnd <= (int)track->loopStart) { return SCE_ERROR_ATRAC_BAD_CODEC_PARAMS; } } } break; } case FACT_CHUNK_MAGIC: { if (chunkSize < 4) { return SCE_ERROR_ATRAC_UNKNOWN_FORMAT; } track->endSample = Read32(data, &offset); remainingBytes = chunkSize - 4; if (remainingBytes == 4) { track->firstSampleOffset = Read32(data, &offset); remainingBytes = 0; } else if (remainingBytes >= 8) { // Extended FACT format. offset += 4; track->firstSampleOffset = Read32(data, &offset); modifiedSampleOffset = true; remainingBytes -= 8; } break; } default: // Skip the unknown block. INFO_LOG(Log::Atrac, "Skipping unknown block ID %08x at offset %d with size %d", chunkID, offset - 8, chunkSize); break; } if (remainingBytes) { INFO_LOG(Log::Atrac, "%08x chunk has %d extra bytes", chunkID, remainingBytes); } offset = nextOffset; } } struct AA3Info { int tagSize; int dataOff; int extraBytes; u8 codecType; // 1 == at3plus u32 codecParams; u32 waveDataSize; }; static u16 ParseU16BE(const u8 *data, int *offset) { const u16 value = (data[*offset] << 8) | data[*offset + 1]; *offset += 2; return value; } static u32 Parse3BytesBE(const u8 *data, int *offset) { const u32 id = (data[*offset] << 16) | (data[*offset + 1] << 8) | data[*offset + 2]; *offset += 3; return id; } static u32 Parse28BitIntBE(const u8 *data, int *offset) { const u32 value = ((data[*offset] & 0x7F) << 21) | ((data[*offset + 1] & 0x7F) << 14) | ((data[*offset + 2] & 0x7F) << 7) | (data[*offset + 3] & 0x7F); *offset += 4; return value; } static u32 Parse4BytesBE(const u8 *data, int *offset) { const u32 value = (data[*offset] << 24) | (data[*offset + 1] << 16) | (data[*offset + 2] << 8) | data[*offset + 3]; *offset += 4; return value; } static u32 ParseAA3Headers(u32 readSize, AA3Info *info, u32 fileSize, const u8 *aa3Data) { if ((u32)readSize < 9) { return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL; } int offset = 0; const u32 id = Parse3BytesBE(aa3Data, &offset); if (id == 0x656133 || id == 0x494433) { // "ea3" or "id3" const u8 *dataPtr = aa3Data + offset; if ((*dataPtr != 3) || (offset = offset + 3, dataPtr[1] != 0)) { return SCE_ERROR_ATRAC_AA3_OTHER_FAILURE; } info->tagSize = Parse28BitIntBE(aa3Data, &offset); if (info->tagSize == 0xffffffff) { return SCE_ERROR_ATRAC_AA3_INVALID_DATA; } offset += info->tagSize; if (offset >= readSize) { return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL; } if (aa3Data[offset] == 0) { offset += 0x10; } info->dataOff = offset; } else { // Might be that the file just starts with the EA3 header. info->tagSize = 0; info->dataOff = 0; offset = 0; } if (offset + 0x22 > readSize) { return SCE_ERROR_ATRAC_AA3_SIZE_TOO_SMALL; } if (Parse3BytesBE(aa3Data, &offset) != 0x454133) { return SCE_ERROR_ATRAC_AA3_INVALID_DATA; } offset++; info->extraBytes = ParseU16BE(aa3Data, &offset);; if (ParseU16BE(aa3Data, &offset) != 0xffff) { return SCE_ERROR_ATRAC_AA3_INVALID_DATA; } offset += 24; info->codecType = aa3Data[offset]; u32 codec; switch (info->codecType) { case 0: codec = PSP_CODEC_AT3; break; case 1: codec = PSP_CODEC_AT3PLUS; break; default: return SCE_ERROR_ATRAC_AA3_INVALID_DATA; } info->codecParams = Parse4BytesBE(aa3Data, &offset); const u32 dataOff = info->dataOff + info->extraBytes; info->waveDataSize = fileSize - dataOff; info->dataOff = dataOff; return codec; } static int ConvertAA3InfoToTrackInfo(const AA3Info *in, TrackInfo *out) { out->loopEnd = 0xffffffff; out->loopStart = 0xffffffff; out->waveDataSize = in->waveDataSize; out->dataOff = in->dataOff; out->numChans = 2; out->endSample = 0; const u32 codecParams = in->codecParams; if (in->codecType == 0) { if ((codecParams & 0xe000) != 0x2000) { return SCE_ERROR_ATRAC_AA3_BAD_CODEC_PARAMS; } out->firstSampleOffset = 0x400; out->blockAlign = (u16)((codecParams & 0x3ff) << 3); if ((codecParams & 0x20000) == 0) { out->sampleSizeMaybe = 0; } else { out->sampleSizeMaybe = 1; } out->tailFlag = 0; return PSP_CODEC_AT3; } else if (in->codecType == 1) { if ((codecParams & 0x1c00) != 0x800 || (codecParams & 0xe000) != 0x2000) { return SCE_ERROR_ATRAC_AA3_BAD_CODEC_PARAMS; } out->firstSampleOffset = 0x800; out->blockAlign = ((u16)codecParams & 0x3ff) * 8 + 8; out->sampleSizeMaybe = (u8)(((codecParams >> 8) & 0x3) | 0x28); out->tailFlag = codecParams & 0xFF; return PSP_CODEC_AT3PLUS; } else { return SCE_ERROR_ATRAC_AA3_INVALID_DATA; } } int ParseAA3(const u8 *buffer, u32 readSize, u32 fileSize, TrackInfo *track) { AA3Info info; int retval = ParseAA3Headers(readSize, &info, fileSize, buffer); if (retval < 0) { return retval; } return ConvertAA3InfoToTrackInfo(&info, track); }