mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-03 03:05:18 +02:00
These parsers run on fully game-controlled buffers (reachable via the various sceAtracSetData*/sceAtracSetHalfwayBuffer* HLE calls), so a malicious/malformed game can supply arbitrary bytes here: - AnalyzeAtracTrack's RIFF chunk-walking loop computed `offset += chunk + (chunk & 1)` (all in 32-bit) and only bounds-checked afterwards. A crafted chunk size could wrap `offset` (and the `offset + 12` check itself) around, bypassing the bounds check entirely - Read32(), whose offset parameter is a plain `int`, would then read from a wild pointer far outside the buffer. Do the validation in 64-bit before mutating offset, mirroring the pattern already used by the newer ParseWaveAT3 parser. - AnalyzeAA3Track validated `size >= tagSize + 36` but then read up to relative index 35 after rebasing by 10+tagSize - i.e. absolute index tagSize+45, 10 bytes past what was actually checked. - The SMPL chunk's loop count (checkNumLoops) was only checked for being negative, not bounded against the chunk's actual size, so a crafted value near INT_MAX would drive an unbounded (up to ~2 billion entry) vector::resize() - an easy crash/OOM. The same unclamped value also let the fill loop below run past the end of the chunk, since its bound compares the loop counter to chunkSize rather than the byte offset actually being advanced (24 bytes/loop). Clamping checkNumLoops to what the chunk can actually hold fixes both. - ParseAA3Headers checked for at least 9 bytes but the "ea3"/"id3" branch it guards reads up through byte index 9, needing 10.
719 lines
23 KiB
C++
719 lines
23 KiB
C++
#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);
|
|
// Validate and bounds-check in 64-bit *before* mutating offset (a u32).
|
|
// A negative or huge chunk could otherwise wrap offset (and offset + 12)
|
|
// around, bypassing the bounds check and making Read32() (whose offset
|
|
// parameter is a plain `int`) read from a wild pointer far outside buffer.
|
|
if (chunk < 0 || (u64)offset + (u64)chunk + (chunk & 1) + 12 > size) {
|
|
*error = StringFromFormat("%d too small for WAVE chunk at offset %d", size, offset);
|
|
return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
|
|
}
|
|
// Round the chunk size up to the nearest 2.
|
|
offset += chunk + (chunk & 1);
|
|
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.
|
|
// But the reads below must stay within mapped guest memory: clamp the
|
|
// parse bound to the actual mapped region at the buffer, so a crafted,
|
|
// inflated RIFF size can't push the reads past the end of RAM.
|
|
u32 maxSize = std::max(track->fileSize, size);
|
|
const u32 bufferAddr = Memory::GetAddressFromHostPointer(buffer);
|
|
if (bufferAddr != 0) {
|
|
u32 maxSizeAtAddr = Memory::MaxSizeAtAddress(bufferAddr);
|
|
if (maxSize > maxSizeAtAddr) {
|
|
// This is not a big deal - it's ok for the DATA chunk to be larger than RAM as it will
|
|
// get streamed in in a looping fashion. Note below where we check maxSize, we allow the DATA chunk to extend like that.
|
|
DEBUG_LOG(Log::ME, "AnalyzeTrack: RIFF size %d exceeds mapped memory at %08x (%d)", maxSize, bufferAddr, maxSizeAtAddr);
|
|
maxSize = maxSizeAtAddr;
|
|
}
|
|
}
|
|
|
|
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) && chunkMagic != DATA_CHUNK_MAGIC) {
|
|
ERROR_LOG(Log::Atrac, "Chunk size %d exceeds available data and is not DATA", chunkSize);
|
|
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;
|
|
}
|
|
// checkNumLoops is otherwise just an unvalidated field from the file - left
|
|
// unclamped, it could both drive an unbounded (up to ~2 billion entry)
|
|
// allocation here, and (since the loop below compares the loop counter `i`
|
|
// against chunkSize, rather than the byte offset actually being advanced by
|
|
// 24 per iteration) let reads run well past the end of this chunk. Clamp it
|
|
// to how many 24-byte loop entries could actually fit.
|
|
u32 maxLoops = chunkSize >= 36 ? (chunkSize - 36) / 24 : 0;
|
|
if ((u32)checkNumLoops > maxLoops) {
|
|
checkNumLoops = (int)maxLoops;
|
|
}
|
|
|
|
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);
|
|
// After rebasing buffer by 10+tagSize below, the codecParams/codec type reads
|
|
// go up to relative index 35 (buffer[33..35] and buffer[32]) - i.e. absolute
|
|
// index tagSize+45 - so the required size is tagSize+46, not just tagSize+36
|
|
// (which only covers up through the EA3 magic check).
|
|
if (size < tagSize + 46) {
|
|
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;
|
|
}
|
|
// Guard against underflow (blockSize < 4) making the offset negative
|
|
// and bypassing the loop bounds check, and against advancing past the
|
|
// end of the buffer.
|
|
if (blockSize < 4 || (u64)offset + blockSize - 4 > dataLength) {
|
|
ERROR_LOG(Log::Atrac, "Invalid RIFF block size %d at offset %d: less than 4 or exceeds data length (%d)", blockSize, offset, dataLength);
|
|
return SCE_ERROR_ATRAC_SIZE_TOO_SMALL;
|
|
}
|
|
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) {
|
|
// When the "ea3"/"id3" branch below is taken, Parse28BitIntBE() ends up
|
|
// reading aa3Data[6..9], so 9 bytes isn't quite enough - need 10.
|
|
if ((u32)readSize < 10) {
|
|
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);
|
|
}
|