#include "meta.h" #include "../layout/layout.h" #include "../coding/coding.h" /* for reading integers inexplicably packed into 80-bit ('double extended') floats */ static uint32_t read80bitSANE(off_t offset, STREAMFILE *streamFile) { uint8_t buf[0x0a]; int32_t exponent; int32_t mantissa; int i; if (read_streamfile(buf,offset,0x0a,streamFile) != 0x0a) return 0; exponent = ((buf[0]<<8)|(buf[1]))&0x7fff; exponent -= 16383; mantissa = 0; for (i=0;i<8;i++) { int32_t shift = exponent-7-i*8; if (shift >= 0) mantissa |= buf[i+2] << shift; else if (shift > -8) mantissa |= buf[i+2] >> -shift; } return mantissa*((buf[0]&0x80)?-1:1); } static uint32_t find_marker(STREAMFILE *streamFile, off_t mark_offset, int marker_id) { uint16_t marker_count; int i; off_t marker_offset; marker_count = read_16bitBE(mark_offset+8,streamFile); marker_offset = mark_offset+10; for (i=0;i file_size) goto fail; switch(chunk_type) { case 0x46564552: /* "FVER" (version info) */ if (fver_found) goto fail; if (is_aiff) goto fail; /* plain AIFF shouldn't have */ fver_found = 1; /* specific size */ if (chunk_size != 4) goto fail; /* Version 1 of AIFF-C spec timestamp */ if ((uint32_t)read_32bitBE(current_chunk+0x08,streamFile) != 0xA2805140) goto fail; break; case 0x434F4D4D: /* "COMM" (main header) */ if (comm_found) goto fail; comm_found = 1; channel_count = read_16bitBE(current_chunk+8,streamFile); if (channel_count <= 0) goto fail; sample_count = (uint32_t)read_32bitBE(current_chunk+0x0a,streamFile); /* sometimes number of blocks */ sample_size = read_16bitBE(current_chunk+0x0e,streamFile); sample_rate = read80bitSANE(current_chunk+0x10,streamFile); if (is_aifc) { uint32_t codec = read_32bitBE(current_chunk+0x1a,streamFile); switch (codec) { case 0x53445832: /* "SDX2" [3DO games: Super Street Fighter II Turbo (3DO), etc] */ coding_type = coding_SDX2; interleave = 0x01; break; case 0x43424432: /* "CBD2" [M2 (arcade 3DO) games: IMSA Racing (M2), etc] */ coding_type = coding_CBD2; interleave = 0x01; break; case 0x41445034: /* "ADP4" */ coding_type = coding_DVI_IMA_int; if (channel_count != 1) break; /* don't know how stereo DVI is laid out */ break; case 0x696D6134: /* "ima4" [Alida (PC), Lunar SSS (iOS)] */ coding_type = coding_APPLE_IMA4; interleave = 0x22; sample_count = sample_count * ((interleave-0x2)*2); break; case 0x434F4D50: { /* "COMP" (generic compression) */ uint8_t comp_name[255] = {0}; uint8_t comp_size = read_8bit(current_chunk + 0x1e, streamFile); if (comp_size >= sizeof(comp_name) - 1) goto fail; read_streamfile(comp_name, current_chunk + 0x1f, comp_size, streamFile); if (memcmp(comp_name, "Relic Codec v1.6", comp_size) == 0) { /* Homeworld 2 (PC) */ coding_type = coding_RELIC; sample_count = sample_count * 512; } else { goto fail; } break; } default: VGM_LOG("AIFC: unknown codec\n"); goto fail; } /* string size and human-readable AIFF-C codec follows */ } else if (is_aiff) { switch (sample_size) { case 8: coding_type = coding_PCM8; interleave = 1; break; case 16: coding_type = coding_PCM16BE; interleave = 2; break; case 4: /* Crusader: No Remorse (SAT), Road Rash (3DO) */ coding_type = coding_XA; break; default: VGM_LOG("AIFF: unknown codec\n"); goto fail; } } break; case 0x53534E44: /* "SSND" (main data) */ case 0x4150434D: /* "APCM" (main data for XA) */ if (data_found) goto fail; data_found = 1; start_offset = current_chunk + 0x10 + read_32bitBE(current_chunk+0x08,streamFile); /* when "APCM" XA frame size is at 0x0c, fixed to 0x914 */ break; case 0x4D41524B: /* "MARK" (loops) */ if (mark_found) goto fail; mark_found = 1; mark_offset = current_chunk; break; case 0x494E5354: /* "INST" (loops) */ if (inst_found) goto fail; inst_found = 1; inst_offset = current_chunk; break; default: /* spec says we can skip unrecognized chunks */ break; } current_chunk += 0x08+chunk_size; } } if (is_aifc) { if (!fver_found || !comm_found || !data_found) goto fail; } else if (is_aiff) { if (!comm_found || !data_found) goto fail; } /* read loop points */ if (inst_found && mark_found) { int start_marker; int end_marker; /* use the sustain loop */ /* if playMode=ForwardLooping */ if (read_16bitBE(inst_offset+16,streamFile) == 1) { start_marker = read_16bitBE(inst_offset+18,streamFile); end_marker = read_16bitBE(inst_offset+20,streamFile); /* check for sustain markers != 0 (invalid marker no) */ if (start_marker && end_marker) { /* find start marker */ loop_start = find_marker(streamFile,mark_offset,start_marker); loop_end = find_marker(streamFile,mark_offset,end_marker); /* find_marker is type uint32_t as the spec says that's the type * of the position value, but it returns a -1 on error, and the * loop_start and loop_end variables are int32_t, so the error * will become apparent. * We shouldn't have a loop point that overflows an int32_t * anyway. */ loop_flag = 1; if (loop_start==loop_end) loop_flag = 0; } } /* Relic has "beg loop" "end loop" comments but no actual looping? */ } /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = sample_rate; vgmstream->num_samples = sample_count; vgmstream->loop_start_sample = loop_start; vgmstream->loop_end_sample = loop_end; vgmstream->coding_type = coding_type; switch(coding_type) { case coding_XA: vgmstream->layout_type = layout_blocked_xa_aiff; /* AIFF XA can use sample rates other than 37800/18900 */ /* some Crusader: No Remorse tracks have XA headers with incorrect 0xFF, rip bug/encoder feature? */ break; case coding_RELIC: { int bitrate = read_16bitBE(start_offset, streamFile); start_offset += 0x02; vgmstream->codec_data = init_relic(channel_count, bitrate, sample_rate); if (!vgmstream->codec_data) goto fail; vgmstream->layout_type = layout_none; vgmstream->sample_rate = 44100; /* fixed output */ break; } default: vgmstream->layout_type = (channel_count > 1) ? layout_interleave : layout_none; vgmstream->interleave_block_size = interleave; break; } if (is_aifc) vgmstream->meta_type = meta_AIFC; else if (is_aiff) vgmstream->meta_type = meta_AIFF; if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }