/* MIB+MIH - SCEE MultiStream interleaved bank (header+data) [namCollection: Ace Combat 2 (PS2), Rampage: Total Destruction (PS2)] */ VGMSTREAM * init_vgmstream_mib_mih(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; STREAMFILE * streamHeader = NULL; off_t start_offset; size_t data_size, frame_size, frame_last, frame_count; int channel_count, loop_flag; /* check extension */ if (!check_extensions(streamFile, "mib")) goto fail; streamHeader = open_streamfile_by_ext(streamFile,"mih"); if (!streamHeader) goto fail; if (read_32bitBE(0x00,streamHeader) != 0x40000000) /* header size */ goto fail; loop_flag = 0; /* MIB+MIH don't PS-ADPCM loop flags */ channel_count = read_32bitLE(0x08,streamHeader); start_offset = 0x00; /* 0x04: padding (0x20, MIH header must be multiple of 0x40) */ frame_last = (uint16_t)read_16bitLE(0x05,streamHeader); frame_size = read_32bitLE(0x10,streamHeader); frame_count = read_32bitLE(0x14,streamHeader); if (frame_count == 0) { /* rarely [Gladius (PS2)] */ frame_count = get_streamfile_size(streamFile) / (frame_size * channel_count); } data_size = frame_count * frame_size; data_size -= frame_last ? (frame_size-frame_last) : 0; /* last frame has less usable data */ data_size *= channel_count; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = read_32bitLE(0x0C,streamHeader); vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count); vgmstream->meta_type = meta_PS2_MIB_MIH; vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = frame_size; if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; close_streamfile(streamHeader); return vgmstream; fail: close_streamfile(streamHeader); close_vgmstream(vgmstream); return NULL; }
/* 04SW - found in Driver: Parallel Lines (Wii) */ VGMSTREAM * init_vgmstream_wii_04sw(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count; size_t file_size, data_size; /* checks */ /* ".04sw" is just the ID, the real filename inside the file uses .XA */ if (!check_extensions(streamFile,"xa,04sw")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x30345357) /* "04SW" */ goto fail; /* after the ID goes a semi-standard DSP header */ if (read_32bitBE(0x10,streamFile) != 0) goto fail; /* should be non looping */ loop_flag = 0; /* not in header it seems so just dual header check */ channel_count = (read_32bitBE(0x04,streamFile) == read_32bitBE(0x64,streamFile)) ? 2 : 1; start_offset = read_32bitBE(0x04 + 0x60*channel_count,streamFile); file_size = get_streamfile_size(streamFile); data_size = read_32bitBE(0x04 + 0x60*channel_count + 0x04,streamFile); if (data_size+start_offset != file_size) goto fail; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = read_32bitBE(0x0c,streamFile); vgmstream->num_samples = read_32bitBE(0x04,streamFile); vgmstream->coding_type = coding_NGC_DSP; vgmstream->layout_type = channel_count == 1 ? layout_none : layout_interleave; vgmstream->interleave_block_size = 0x8000; vgmstream->interleave_last_block_size = (read_32bitBE(0x08,streamFile) / 2 % vgmstream->interleave_block_size + 7) / 8 * 8; dsp_read_coefs_be(vgmstream,streamFile,0x20, 0x60); /* the initial history offset seems different thatn standard DSP and possibly always zero */ vgmstream->meta_type = meta_WII_04SW; /* the rest of the header has unknown values (several repeats) and the filename */ if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* MSA (from Psyvariar -Complete Edition-) */ VGMSTREAM * init_vgmstream_ps2_msa(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset, datasize, filesize; int loop_flag, channel_count; /* check extension, case insensitive */ if (!check_extensions(streamFile, "msa")) goto fail; /* check header */ if (read_32bitBE(0x00,streamFile) != 0x00000000) goto fail; loop_flag = 0; channel_count = 2; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; start_offset = 0x14; datasize = read_32bitLE(0x4,streamFile); filesize = get_streamfile_size(streamFile); vgmstream->channels = channel_count; vgmstream->sample_rate = read_32bitLE(0x10,streamFile); vgmstream->num_samples = datasize*28/(16*channel_count); vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x4000; vgmstream->meta_type = meta_PS2_MSA; /* MSAs are strangely truncated, so manually calculate samples * data after last usable block is always silence or garbage */ if (datasize > filesize) { off_t usable_size = filesize - start_offset; usable_size -= usable_size % (vgmstream->interleave_block_size*channel_count);/* block-aligned */ vgmstream->num_samples = usable_size * 28 / (16*channel_count); } /* open the file for reading */ if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* .ASS - from Dai Senryaku VII: Exceed (PS2) */ VGMSTREAM * init_vgmstream_ps2_ass(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; size_t channel_size, interleave; int loop_flag, channel_count, sample_rate; int32_t num_samples, loop_start = 0, loop_end = 0; /* checks */ if (!check_extensions(streamFile, "ass")) goto fail; start_offset = 0x800; channel_count = read_32bitLE(0x00,streamFile); /* assumed */ if (channel_count != 2) goto fail; sample_rate = read_32bitLE(0x04,streamFile); channel_size = read_32bitLE(0x08,streamFile); interleave = read_32bitLE(0x0c,streamFile); num_samples = ps_bytes_to_samples(channel_size,1); loop_flag = ps_find_loop_offsets(streamFile, start_offset, channel_size*channel_count, channel_count, interleave, &loop_start, &loop_end); loop_flag = loop_flag && (num_samples > 10*sample_rate); /* disable looping for smaller files (in seconds) */ /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->meta_type = meta_PS2_ASS; vgmstream->sample_rate = sample_rate; vgmstream->num_samples = num_samples; vgmstream->loop_start_sample = loop_start; vgmstream->loop_end_sample = loop_end; vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = interleave; if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* .SMV - from Cho Aniki Zero (PSP) */ VGMSTREAM * init_vgmstream_smv(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count; size_t channel_size, loop_start; /* check extension */ if (!check_extensions(streamFile, "smv")) goto fail; channel_size = read_32bitLE(0x00,streamFile); /* 0x08: number of full interleave blocks */ channel_count = read_16bitLE(0x0a,streamFile); loop_start = read_32bitLE(0x18,streamFile); loop_flag = (loop_start != -1); start_offset = 0x800; if (channel_size * channel_count + start_offset != get_streamfile_size(streamFile)) goto fail; channel_size -= 0x10; /* last value has SPU end frame without flag 0x7 as it should */ /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count, loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = read_32bitLE(0x10, streamFile); vgmstream->num_samples = ps_bytes_to_samples(channel_size*channel_count, channel_count); vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start*channel_count, channel_count); vgmstream->loop_end_sample = vgmstream->num_samples; vgmstream->meta_type = meta_SMV; vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = read_32bitLE(0x04, streamFile); vgmstream->interleave_last_block_size = read_32bitLE(0x0c, streamFile); if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* NXAP - Nex Entertainment header [Time Crisis 4 (PS3), Time Crisis Razing Storm (PS3)] */ VGMSTREAM * init_vgmstream_nxap(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count; /* checks */ if (!check_extensions(streamFile, "adp")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x4E584150) /* "NXAP" */ goto fail; if (read_32bitLE(0x14,streamFile) != 0x40 || /* expected frame size? */ read_32bitLE(0x18,streamFile) != 0x40) /* expected interleave? */ goto fail; start_offset = read_32bitLE(0x04,streamFile); channel_count = read_32bitLE(0x0c,streamFile); loop_flag = 0; //(read_32bitLE(0x24,streamFile) > 0); //todo /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count, loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = read_32bitLE(0x10, streamFile); vgmstream->num_samples = read_32bitLE(0x1c,streamFile) * (0x40-0x04)*2 / channel_count; /* number of frames */ /* unknown loop format, also 0x28/2c values seem related */ //vgmstream->loop_start_sample = read_32bitLE(0x20,streamFile) * (0x40-0x04)*2 / channel_count; //vgmstream->loop_end_sample = read_32bitLE(0x24,streamFile) * (0x40-0x04)*2 / channel_count; //vgmstream->loop_end_sample = vgmstream->loop_start_sample + vgmstream->loop_end_sample; vgmstream->meta_type = meta_NXAP; vgmstream->coding_type = coding_NXAP; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x40; if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* .RXW - legacy fake ext/header for poorly split XWH+XWB files generated by old tools (incorrect header/chunk sizes) */ VGMSTREAM * init_vgmstream_ps2_rxw(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; int loop_flag=0, channel_count; off_t start_offset; /* check extension, case insensitive */ if (!check_extensions(streamFile,"rxw")) goto fail; /* check RXWS/FORM Header */ if (!((read_32bitBE(0x00,streamFile) == 0x52585753) && (read_32bitBE(0x10,streamFile) == 0x464F524D))) goto fail; loop_flag = (read_32bitLE(0x3C,streamFile)!=0xFFFFFFFF); channel_count=2; /* Always stereo files */ /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = read_32bitLE(0x2E,streamFile); vgmstream->num_samples = (read_32bitLE(0x38,streamFile)*28/16)/2; /* Get loop point values */ if(vgmstream->loop_flag) { vgmstream->loop_start_sample = read_32bitLE(0x3C,streamFile)/16*14; vgmstream->loop_end_sample = read_32bitLE(0x38,streamFile)/16*14; } vgmstream->interleave_block_size = read_32bitLE(0x1c,streamFile)+0x10; vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->meta_type = meta_PS2_RXWS; start_offset = 0x40; /* open the file for reading */ if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* STRM - from Final Fantasy Tactics A2 (NDS) */ VGMSTREAM * init_vgmstream_nds_strm_ffta2(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count; /* checks*/ /* .bin: actual extension * .strm: header id */ if (!check_extensions(streamFile,"bin,strm")) goto fail; /* check header */ if (read_32bitBE(0x00,streamFile) != 0x52494646 || /* "RIFF" */ read_32bitBE(0x08,streamFile) != 0x494D4120) /* "IMA " */ goto fail; loop_flag = (read_32bitLE(0x20,streamFile) !=0); channel_count = read_32bitLE(0x24,streamFile); start_offset = 0x2C; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->channels = channel_count; vgmstream->sample_rate = read_32bitLE(0x0C,streamFile); vgmstream->num_samples = (read_32bitLE(0x04,streamFile)-start_offset); vgmstream->loop_start_sample = read_32bitLE(0x20,streamFile); vgmstream->loop_end_sample = read_32bitLE(0x28,streamFile); vgmstream->meta_type = meta_NDS_STRM_FFTA2; vgmstream->coding_type = coding_FFTA2_IMA; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x80; if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* .PCM - KCE Japan East PS2 games (Ephemeral Fantasia, Yu-Gi-Oh! The Duelists of the Roses, 7 Blades) */ VGMSTREAM * init_vgmstream_ps2_pcm(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count; /* check extension */ if ( !check_extensions(streamFile,"pcm") ) goto fail; /* check header (data_size vs num_samples) */ if (pcm_bytes_to_samples(read_32bitLE(0x00,streamFile), 2, 16) != read_32bitLE(0x04,streamFile)) goto fail; /* should work too */ //if (read_32bitLE(0x00,streamFile)+0x800 != get_streamfile_size(streamFile)) // goto fail; loop_flag = (read_32bitLE(0x0C,streamFile) != 0x00); channel_count = 2; start_offset = 0x800; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->channels = channel_count; vgmstream->sample_rate = 24000; vgmstream->num_samples = read_32bitLE(0x04,streamFile); vgmstream->loop_start_sample = read_32bitLE(0x08,streamFile); vgmstream->loop_end_sample = read_32bitLE(0x0C,streamFile); vgmstream->coding_type = coding_PCM16LE; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x2; vgmstream->meta_type = meta_PS2_PCM; /* open the file for reading */ if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: if (vgmstream) close_vgmstream(vgmstream); return NULL; }
/* MUSC - from Krome's PS2 games (The Legend of Spyro, Ty the Tasmanian Tiger) */ VGMSTREAM * init_vgmstream_musc(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; int loop_flag, channel_count; off_t start_offset; size_t data_size; /* .mus is the real extension, .musc is the header ID */ if (!check_extensions(streamFile,"mus,musc")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x4D555343) /* "MUSC" */ goto fail; start_offset = read_32bitLE(0x10,streamFile); data_size = read_32bitLE(0x14,streamFile); if (start_offset + data_size != get_streamfile_size(streamFile)) goto fail; /* always does full loops unless it ends in silence */ loop_flag = read_32bitBE(get_streamfile_size(streamFile) - 0x10,streamFile) != 0x0C000000; channel_count = 2; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = (uint16_t)read_16bitLE(0x06,streamFile); vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count); vgmstream->loop_start_sample = 0; vgmstream->loop_end_sample = vgmstream->num_samples; vgmstream->meta_type = meta_MUSC; vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = read_32bitLE(0x18,streamFile) / 2; if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* .208 - from Ocean game(s?) [Last Rites (PC)] */ VGMSTREAM * init_vgmstream_208(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count, sample_rate; size_t data_size; /* checks */ if (!check_extensions(streamFile, "208")) goto fail; /* possible validation: (0x04 == 0 and 0xcc == 0x1F7D984D) or 0x04 == 0xf0 and 0xcc == 0) */ if (!((read_32bitLE(0x04,streamFile) == 0x00 && read_32bitBE(0xcc,streamFile) == 0x1F7D984D) || (read_32bitLE(0x04,streamFile) == 0xF0 && read_32bitBE(0xcc,streamFile) == 0x00000000))) goto fail; start_offset = read_32bitLE(0x00,streamFile); data_size = read_32bitLE(0x0c,streamFile); sample_rate = read_32bitLE(0x34,streamFile); channel_count = read_32bitLE(0x3C,streamFile); /* assumed */ loop_flag = 0; if (start_offset + data_size != get_streamfile_size(streamFile)) goto fail; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count, loop_flag); if (!vgmstream) goto fail; vgmstream->meta_type = meta_208; vgmstream->sample_rate = sample_rate; vgmstream->num_samples = pcm_bytes_to_samples(data_size, channel_count, 8); vgmstream->coding_type = coding_PCM8_U; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x1; if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* .VAI - from Asobo Studio games [Ratatouille (GC)] */ VGMSTREAM * init_vgmstream_vai(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; size_t data_size; int loop_flag, channel_count; /* checks */ if ( !check_extensions(streamFile,"vai") ) goto fail; start_offset = 0x4060; data_size = get_streamfile_size(streamFile) - start_offset; if (read_32bitBE(0x04,streamFile) != data_size) goto fail; channel_count = 2; loop_flag = 0; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->meta_type = meta_VAI; vgmstream->sample_rate = read_32bitBE(0x00,streamFile); vgmstream->num_samples = dsp_bytes_to_samples(data_size,channel_count); vgmstream->coding_type = coding_NGC_DSP; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x4000; dsp_read_coefs_be(vgmstream,streamFile,0x0c,0x20); if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
VGMSTREAM * init_vgmstream_ngc_adpdtk(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset = 0; int channel_count = 2, loop_flag = 0; /* always stereo, no loop */ /* check extension, case insensitive */ if ( !check_extensions(streamFile,"dtk,adp")) goto fail; /* .adp files have no header, and the ext is common, so all we can do is look for valid first frames */ if (check_extensions(streamFile,"adp")) { int i; for (i = 0; i < 10; i++) { /* try a bunch of frames */ if (read_8bit(0x00 + i*0x20,streamFile) != read_8bit(0x02 + i*0x20,streamFile) || read_8bit(0x01 + i*0x20,streamFile) != read_8bit(0x03 + i*0x20,streamFile)) goto fail; } } /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count, loop_flag); if (!vgmstream) goto fail; vgmstream->num_samples = get_streamfile_size(streamFile) / 32 * 28; vgmstream->sample_rate = 48000; vgmstream->coding_type = coding_NGC_DTK; vgmstream->layout_type = layout_none; vgmstream->meta_type = meta_NGC_ADPDTK; /* open the file for reading */ if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* A2M - from Artificial Mind & Movement games [Scooby-Doo! Unmasked (PS2)] */ VGMSTREAM * init_vgmstream_a2m(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; size_t data_size; int loop_flag, channel_count; /* checks */ if ( !check_extensions(streamFile,"int") ) goto fail; if (read_32bitBE(0x00,streamFile) != 0x41324D00) /* "A2M\0" */ goto fail; if (read_32bitBE(0x04,streamFile) != 0x50533200) /* "PS2\0" */ goto fail; start_offset = 0x30; data_size = get_streamfile_size(streamFile) - start_offset; channel_count = 2; loop_flag = 0; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->meta_type = meta_A2M; vgmstream->sample_rate = read_32bitBE(0x10,streamFile); vgmstream->num_samples = ps_bytes_to_samples(data_size,channel_count); vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x6000; if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* ASTL - found in Dead Rising (PC) */ VGMSTREAM * init_vgmstream_pc_ast(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset, data_size; int loop_flag, channel_count; /* check extension, case insensitive */ if ( !check_extensions(streamFile,"ast")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x4153544C) /* "ASTL" */ goto fail; loop_flag = 0; //TODO - Find hidden loop point calc and flag channel_count = read_8bit(0x32, streamFile); data_size = read_32bitLE(0x20,streamFile); /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; /* TODO - Find non-obvious loop points and flag (if any) */ start_offset = read_32bitLE(0x10,streamFile); vgmstream->sample_rate = read_32bitLE(0x34,streamFile); vgmstream->coding_type = coding_PCM16LE; vgmstream->num_samples = data_size/(channel_count*2); vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x2; vgmstream->meta_type = meta_PC_AST; /* open the file for reading */ if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* Maxis XA - found in Sim City 3000 (PC) */ VGMSTREAM * init_vgmstream_maxis_xa(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count; /* check extension, case insensitive */ if (!check_extensions(streamFile,"xa")) goto fail; /* check header */ if ((read_32bitBE(0x00,streamFile) != 0x58414900) && /* "XAI\0" */ (read_32bitBE(0x00,streamFile) != 0x58414A00)) /* "XAJ\0" (some odd song uses this, no apparent diffs) */ goto fail; loop_flag = 0; channel_count = read_16bitLE(0x0A,streamFile); start_offset = 0x18; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = read_32bitLE(0x0C,streamFile); vgmstream->num_samples = read_32bitLE(0x04,streamFile)/2/channel_count; vgmstream->meta_type = meta_MAXIS_XA; vgmstream->coding_type = coding_MAXIS_XA; vgmstream->layout_type = layout_none; /* open streams */ if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* AL" - headerless a-law, found in Conquest of Elysium 3 (PC) */ VGMSTREAM * init_vgmstream_pc_al2(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag = 0, channel_count; if ( !check_extensions(streamFile,"al2")) goto fail; channel_count = 2; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = 22050; vgmstream->coding_type = coding_ALAW; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x01; vgmstream->meta_type = meta_PC_AL2; vgmstream->num_samples = pcm_bytes_to_samples(get_streamfile_size(streamFile), channel_count, 8); if (loop_flag) { vgmstream->loop_start_sample = 0; vgmstream->loop_end_sample = vgmstream->num_samples; } start_offset = 0; if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* .XWC - Starbreeze games [Chronicles of Riddick: Assault on Dark Athena, Syndicate] */ VGMSTREAM * init_vgmstream_xwc(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset, extra_offset; size_t data_size; int loop_flag, channel_count, codec, num_samples; /* checks */ /* .xwc: extension of the bigfile, individual files don't have one */ if ( !check_extensions(streamFile,"xwc")) goto fail; /* version */ if (read_32bitBE(0x00,streamFile) == 0x00030000 && read_32bitBE(0x04,streamFile) == 0x00900000) { /* The Darkness */ data_size = read_32bitLE(0x08, streamFile) + 0x1c; /* not including subheader */ channel_count = read_32bitLE(0x0c, streamFile); /* 0x10: num_samples */ /* 0x14: 0x8000? */ /* 0x18: null */ codec = read_32bitBE(0x1c, streamFile); num_samples = read_32bitLE(0x20, streamFile); /* 0x24: config data >> 2? (0x00(1): channels; 0x01(2): ?, 0x03(2): sample_rate) */ extra_offset = 0x28; } else if (read_32bitBE(0x00,streamFile) == 0x00040000 && read_32bitBE(0x04,streamFile) == 0x00900000) { /* Riddick, Syndicate */ data_size = read_32bitLE(0x08, streamFile) + 0x24; /* not including subheader */ channel_count = read_32bitLE(0x0c, streamFile); /* 0x10: num_samples */ /* 0x14: 0x8000? */ codec = read_32bitBE(0x24, streamFile); num_samples = read_32bitLE(0x28, streamFile); /* 0x2c: config data >> 2? (0x00(1): channels; 0x01(2): ?, 0x03(2): sample_rate) */ /* 0x30+: codec dependant */ extra_offset = 0x30; } else { goto fail; } loop_flag = 0; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->meta_type = meta_XWC; vgmstream->num_samples = num_samples; switch(codec) { #ifdef VGM_USE_MPEG case 0x4D504547: { /* "MPEG" (PS3) */ mpeg_custom_config cfg = {0}; start_offset = 0x800; vgmstream->num_samples = read_32bitLE(extra_offset+0x00, streamFile); /* with encoder delay */ //todo improve cfg.data_size = read_32bitLE(extra_offset+0x04, streamFile); /* without padding */ vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_STANDARD, &cfg); if (!vgmstream->codec_data) goto fail; vgmstream->layout_type = layout_none; vgmstream->sample_rate = ((mpeg_codec_data*)vgmstream->codec_data)->sample_rate_per_frame; break; } #endif #ifdef VGM_USE_FFMPEG case 0x584D4100: { /* "XMA\0" (X360) */ uint8_t buf[0x100]; int32_t bytes, seek_size, block_size, block_count, sample_rate; seek_size = read_32bitLE(extra_offset+0x00, streamFile); start_offset = extra_offset+0x04 + seek_size + read_32bitLE(extra_offset+0x04+seek_size, streamFile) + 0x08; start_offset += (start_offset % 0x800) ? 0x800 - (start_offset % 0x800) : 0; /* padded */ data_size = data_size - start_offset; sample_rate = read_32bitBE(extra_offset+0x04+seek_size+0x10, streamFile); block_size = read_32bitBE(extra_offset+0x04+seek_size+0x1c, streamFile); block_count = read_32bitBE(extra_offset+0x04+seek_size+0x28, streamFile); /* others: scrambled RIFF fmt BE values */ bytes = ffmpeg_make_riff_xma2(buf,0x100, vgmstream->num_samples, data_size, vgmstream->channels, sample_rate, block_count, block_size); vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,data_size); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; vgmstream->sample_rate = sample_rate; xma_fix_raw_samples(vgmstream, streamFile, start_offset,data_size, 0, 0,0); /* samples are ok, fix delay */ break; } case 0x564F5242: { /* "VORB" (PC) */ start_offset = 0x30; data_size = data_size - start_offset; vgmstream->codec_data = init_ffmpeg_offset(streamFile, start_offset,data_size); if ( !vgmstream->codec_data ) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; vgmstream->sample_rate = read_32bitLE(start_offset + 0x28, streamFile); break; } #endif default: goto fail; } if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* EA SCHl with fixed header - from EA games (~1997? ex. NHL 97 PC) */ VGMSTREAM * init_vgmstream_ea_schl_fixed(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; size_t header_size; ea_header ea = {0}; /* check extension */ if (!check_extensions(streamFile,"asf")) goto fail; /* check header (see ea_schl.c for more info about blocks) */ if (read_32bitBE(0x00,streamFile) != 0x5343486C) /* "SCHl" */ goto fail; header_size = read_32bitLE(0x04,streamFile); if (!parse_fixed_header(streamFile,&ea, 0x08)) goto fail; start_offset = header_size; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(ea.channels, ea.loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = ea.sample_rate; vgmstream->num_samples = ea.num_samples; //vgmstream->loop_start_sample = ea.loop_start; //vgmstream->loop_end_sample = ea.loop_end; vgmstream->codec_endian = ea.big_endian; vgmstream->meta_type = meta_EA_SCHL_fixed; vgmstream->layout_type = layout_blocked_ea_schl; switch (ea.codec) { case EA_CODEC_PCM: vgmstream->coding_type = ea.bps==8 ? coding_PCM8 : (ea.big_endian ? coding_PCM16BE : coding_PCM16LE); break; case EA_CODEC_IMA: vgmstream->coding_type = coding_DVI_IMA; /* stereo/mono, high nibble first */ break; default: VGM_LOG("EA: unknown codec 0x%02x\n", ea.codec); goto fail; } if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* .OPUS - from Switch games (Lego City Undercover, Ultra SF II, Disgaea 5) */ VGMSTREAM * init_vgmstream_nsw_opus(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag = 0, channel_count; int num_samples = 0, loop_start = 0, loop_end = 0; off_t offset = 0; /* check extension, case insensitive */ if ( !check_extensions(streamFile,"opus,lopus")) /* no relation to Ogg Opus */ goto fail; /* variations, maybe custom */ if (read_32bitBE(0x00,streamFile) == 0x01000080) { /* Lego City Undercover */ offset = 0x00; } else if ((read_32bitBE(0x04,streamFile) == 0x00000000 && read_32bitBE(0x0c,streamFile) == 0x00000000) || (read_32bitBE(0x04,streamFile) == 0xFFFFFFFF && read_32bitBE(0x0c,streamFile) == 0xFFFFFFFF)) { /* Disgaea 5 */ offset = 0x10; loop_start = read_32bitLE(0x00,streamFile); loop_end = read_32bitLE(0x08,streamFile); } else if (read_32bitLE(0x04,streamFile) == 0x02) { /* Ultra Street Fighter II */ offset = read_32bitLE(0x1c,streamFile); num_samples = read_32bitLE(0x00,streamFile); loop_start = read_32bitLE(0x08,streamFile); loop_end = read_32bitLE(0x0c,streamFile); } else { offset = 0x00; } if (read_32bitBE(offset + 0x00,streamFile) != 0x01000080) goto fail; start_offset = offset + 0x28; channel_count = read_8bit(offset + 0x09,streamFile); /* assumed */ /* 0x0a: packet size if CBR?, other values: no idea */ loop_flag = (loop_end > 0); /* -1 when not set */ /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = read_32bitLE(offset + 0x0c,streamFile); vgmstream->meta_type = meta_NSW_OPUS; vgmstream->num_samples = num_samples; vgmstream->loop_start_sample = loop_start; vgmstream->loop_end_sample = loop_end; #ifdef VGM_USE_FFMPEG { uint8_t buf[0x100]; size_t bytes, skip, data_size; ffmpeg_custom_config cfg; data_size = get_streamfile_size(streamFile) - start_offset; skip = 0; //todo bytes = ffmpeg_make_opus_header(buf,0x100, vgmstream->channels, skip, vgmstream->sample_rate); if (bytes <= 0) goto fail; memset(&cfg, 0, sizeof(ffmpeg_custom_config)); cfg.type = FFMPEG_SWITCH_OPUS; vgmstream->codec_data = init_ffmpeg_config(streamFile, buf,bytes, start_offset,data_size, &cfg); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; if (vgmstream->num_samples == 0) vgmstream->num_samples = switch_opus_get_samples(start_offset, data_size, vgmstream->sample_rate, streamFile); } #else goto fail; #endif /* open the file for reading */ if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* Namco NUB xma - from Tekken 6, Galaga Legions DX */ VGMSTREAM * init_vgmstream_nub_xma(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset, chunk_offset; size_t data_size, chunk_size; int loop_flag, channel_count, sample_rate, chunk_type; int num_samples, loop_start_sample, loop_end_sample; /* checks */ if ( !check_extensions(streamFile,"xma")) /* (probably meant to be .nub) */ goto fail; if (read_32bitBE(0x00,streamFile) != 0x786D6100) /* "xma\0" */ goto fail; /* custom header with a "XMA2" or "fmt " chunk inside; most other values are unknown */ chunk_type = read_32bitBE(0xC,streamFile); start_offset = 0x100; data_size = read_32bitBE(0x14,streamFile); chunk_offset = 0xBC; chunk_size = read_32bitBE(0x24,streamFile); if (chunk_type == 0x4) { /* "XMA2" */ xma2_parse_xma2_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample); } else if (chunk_type == 0x8) { /* "fmt " */ channel_count = read_16bitBE(chunk_offset+0x02,streamFile); sample_rate = read_32bitBE(chunk_offset+0x04,streamFile); xma2_parse_fmt_chunk_extra(streamFile, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 1); } else { goto fail; } /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = sample_rate; vgmstream->num_samples = num_samples; vgmstream->loop_start_sample = loop_start_sample; vgmstream->loop_end_sample = loop_end_sample; vgmstream->meta_type = meta_NUB_XMA; #ifdef VGM_USE_FFMPEG { uint8_t buf[0x100]; size_t bytes; if (chunk_type == 0x4) { /* "XMA2" */ bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile); } else { /* "fmt " */ bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile, 1); } vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,data_size); if ( !vgmstream->codec_data ) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; xma_fix_raw_samples(vgmstream, streamFile, start_offset, data_size, chunk_offset, 1,1); /* samples needs adjustment */ } #else goto fail; #endif if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* .VSV - from Square Enix games [Dawn of Mana: Seiken Densetsu 4 (PS2), Kingdom Hearts Re:Chain of Memories (PS2)] */ VGMSTREAM * init_vgmstream_vsv(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; STREAMFILE *temp_streamFile = NULL; off_t start_offset; int loop_flag, channel_count, flags, sample_rate, is_rs; size_t loop_start, adjust, data_size, interleave; /* checks */ /* .vsv: extension from internal filenames [KH Re:CoM (PS2), DoM (PS2), KH HD I.5 + II.5 ReMIX (PS4)] * .psh: fake */ if (!check_extensions(streamFile, "vsv,psh")) goto fail; /* 0x00(1x4): flags/config? */ if ((uint8_t)read_8bit(0x03,streamFile) > 0x64) /* possibly volume */ goto fail; if ((uint8_t)read_8bit(0x0a,streamFile) != 0) /* not seen */ goto fail; /* Romancing SaGa (PS2) uses an earlier? version, this seems to work */ is_rs = ((uint16_t)read_16bitLE(0x00,streamFile) == 0); start_offset = 0x00; /* correct, but needs some tricks to fix sound (see below) */ interleave = 0x800; adjust = (uint16_t)read_16bitLE(0x04,streamFile); loop_start = ((uint16_t)read_16bitLE(0x06,streamFile) & 0x7FFF) * interleave; loop_flag = (uint16_t)read_16bitLE(0x06,streamFile) & 0x8000; /* loop_start != 0 works too, no files loop from beginning to end */ sample_rate = (uint16_t)read_16bitLE(0x08,streamFile); flags = (uint8_t)read_8bit (0x0b,streamFile); /* values: 0x01=stereo, 0x10=mono */ data_size = (uint16_t)read_16bitLE(0x0c,streamFile) * interleave; /* 0x0e: ? (may be a low-ish value) */ channel_count = (flags & 1) ? 2 : 1; /* must discard to avoid wrong loops and unwanted data (easier to see in voices) */ if (!is_rs) { /* RS doesn't do this */ /* adjust & 0xF800 is unknown (values=0x0000|0x0800|0xF800, can be mono/stereo, loop/no, adjust/no) */ size_t discard = adjust & 0x07FF; /* at (file_end - 0x800 + discard) is a 0x03 PS flag to check this (adjust 0 does discard full block) */ data_size -= (0x800 - discard) * channel_count; } /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count, loop_flag); if (!vgmstream) goto fail; vgmstream->meta_type = meta_VSV; vgmstream->sample_rate = sample_rate; vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count); vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channel_count); vgmstream->loop_end_sample = vgmstream->num_samples; /* these loops are odd, but comparing the audio wave with the OSTs values seem correct */ if (is_rs) { vgmstream->loop_start_sample -= ps_bytes_to_samples(channel_count*interleave,channel_count); /* maybe *before* loop block? */ vgmstream->loop_start_sample -= ps_bytes_to_samples(0x200*channel_count,channel_count); /* maybe default adjust? */ } vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = interleave; temp_streamFile = setup_vsv_streamfile(streamFile, start_offset, data_size); if (!temp_streamFile) goto fail; if (!vgmstream_open_stream(vgmstream, temp_streamFile, start_offset)) goto fail; close_streamfile(temp_streamFile); return vgmstream; fail: close_streamfile(temp_streamFile); close_vgmstream(vgmstream); return NULL; }
/* XPCM - from Circus games [Eternal Fantasy (PC), D.C. White Season (PC)] */ VGMSTREAM * init_vgmstream_xpcm(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; size_t decompressed_size; int loop_flag, channel_count, codec, subcodec, sample_rate; /* checks */ if (!check_extensions(streamFile, "pcm")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x5850434D) /* "XPCM" "*/ goto fail; decompressed_size = read_32bitLE(0x04,streamFile); /* (data_size for PCM) */ codec = read_8bit(0x08, streamFile); subcodec = read_8bit(0x09, streamFile); /* 0x0a: always null */ /* 0x0c: always 0x01 (PCM codec) */ channel_count = read_16bitLE(0x0e,streamFile); sample_rate = read_32bitLE(0x10,streamFile); /* 0x14: average bitrate */ /* 0x18: block size */ /* 0x1a: output bits (16) */ start_offset = 0x1c; /* compressed size in codec 0x01/03 */ loop_flag = 0; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count, loop_flag); if (!vgmstream) goto fail; vgmstream->meta_type = meta_XPCM; vgmstream->sample_rate = sample_rate; vgmstream->num_samples = decompressed_size / sizeof(int16_t) / channel_count; switch(codec) { case 0x00: if (subcodec != 0) goto fail; vgmstream->coding_type = coding_PCM16LE; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x02; break; case 0x02: if (subcodec != 0) goto fail; vgmstream->coding_type = coding_CIRCUS_ADPCM; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x01; break; case 0x01: /* LZSS + VQ */ case 0x03: /* unknown */ default: goto fail; } if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* MTA2 - found in Metal Gear Solid 4 (PS3) */ VGMSTREAM * init_vgmstream_mta2(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count, sample_rate; int32_t loop_start, loop_end; uint32_t sample_rate_int; /* checks */ if ( !check_extensions(streamFile,"mta2")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x4d544132) /* "MTA2" */ goto fail; /* allow truncated files for now? */ //if (read_32bitBE(0x04, streamFile) + 0x08 != get_streamfile_size(streamFile)) // goto fail; /* base header (everything is very similar to MGS3's MTAF but BE) */ /* 0x08(4): version? (1), 0x0c(52): null */ /* HEAD chunk */ if (read_32bitBE(0x40, streamFile) != 0x48454144) /* "HEAD" */ goto fail; if (read_32bitBE(0x44, streamFile) != 0xB0) /* HEAD size */ goto fail; /* 0x48(4): null, 0x4c: ? (0x10), 0x50(4): 0x7F (vol?), 0x54(2): 0x40 (pan?) */ channel_count = read_16bitBE(0x56, streamFile); /* counting all tracks */ /* 0x60(4): full block size (0x110 * channels), indirectly channels_per_track = channels / (block_size / 0x110) */ /* 0x80 .. 0xf8: null */ loop_start = read_32bitBE(0x58, streamFile); loop_end = read_32bitBE(0x5c, streamFile); loop_flag = (loop_start != loop_end); /* also flag possibly @ 0x73 */ #if 0 /* those values look like some kind of loop offsets */ if (loop_start/0x100 != read_32bitBE(0x68, streamFile) || loop_end /0x100 != read_32bitBE(0x6C, streamFile) ) { VGM_LOG("MTA2: wrong loop points\n"); goto fail; } #endif sample_rate_int = read_32bitBE(0x7c, streamFile); if (sample_rate_int) { /* sample rate in 32b float (WHY?) typically 48000.0 */ float* sample_float = (float*)&sample_rate_int; sample_rate = (int)*sample_float; } else { /* default when not specified (most of the time) */ sample_rate = 48000; } /* TRKP chunks (x16) */ /* just seem to contain pan/vol stuff (0x7f/0x40), TRKP per track (sometimes +1 main track?) */ /* there is channel layout bitmask at 0x0f (ex. 1ch = 0x04, 3ch = 0x07, 4ch = 0x33, 6ch = 0x3f), surely: * FL 0x01, FR 0x02, FC = 0x04, BL = 0x08, BR = 0x10, BC = 0x20 */ start_offset = 0x800; /* DATA chunk */ if (read_32bitBE(0x7f8, streamFile) != 0x44415441) // "DATA" goto fail; //if (read_32bitBE(0x7fc, streamFile) + start_offset != get_streamfile_size(streamFile)) // goto fail; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = sample_rate; vgmstream->num_samples = loop_end; vgmstream->loop_start_sample = loop_start; vgmstream->loop_end_sample = loop_end; vgmstream->coding_type = coding_MTA2; vgmstream->layout_type = layout_none; vgmstream->meta_type = meta_MTA2; /* open the file for reading */ if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* GTD - found in Knights Contract (X360, PS3), Valhalla Knights 3 (PSV) */ VGMSTREAM * init_vgmstream_gtd(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset, chunk_offset, stpr_offset, name_offset = 0, loop_start_offset, loop_end_offset; size_t data_size, chunk_size; int loop_flag, channel_count, sample_rate; int num_samples, loop_start_sample, loop_end_sample; uint32_t at9_config_data; gtd_codec codec; /* check extension, case insensitive */ if ( !check_extensions(streamFile,"gtd")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x47485320) /* "GHS " */ goto fail; /* header type, not formally specified */ if (read_32bitBE(0x04,streamFile) == 1 && read_16bitBE(0x0C,streamFile) == 0x0166) { /* XMA2 */ /* 0x08(4): seek table size */ chunk_offset = 0x0c; /* custom header with a "fmt " data chunk inside */ chunk_size = 0x34; channel_count = read_16bitBE(chunk_offset+0x02,streamFile); sample_rate = read_32bitBE(chunk_offset+0x04,streamFile); xma2_parse_fmt_chunk_extra(streamFile, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 1); start_offset = read_32bitBE(0x58,streamFile); /* always 0x800 */ data_size = read_32bitBE(0x5c,streamFile); /* 0x34(18): null, 0x54(4): seek table offset, 0x58(4): seek table size, 0x5c(8): null, 0x64: seek table */ stpr_offset = read_32bitBE(chunk_offset+0x54,streamFile) + read_32bitBE(chunk_offset+0x58,streamFile); if (read_32bitBE(stpr_offset,streamFile) == 0x53545052) { /* "STPR" */ name_offset = stpr_offset + 0xB8; /* there are offsets fields but seems to work */ } codec = XMA2; } else if (0x34 + read_32bitLE(0x30,streamFile) + read_32bitLE(0x0c,streamFile) == get_streamfile_size(streamFile)) { /* ATRAC9 */ data_size = read_32bitLE(0x0c,streamFile); start_offset = 0x34 + read_32bitLE(0x30,streamFile); channel_count = read_32bitLE(0x10,streamFile); sample_rate = read_32bitLE(0x14,streamFile); loop_start_offset = read_32bitLE(0x1c, streamFile); loop_end_offset = read_32bitLE(0x20, streamFile); loop_flag = loop_end_offset > loop_start_offset; at9_config_data = read_32bitBE(0x28,streamFile); /* 0x18-0x28: fixed/unknown values */ stpr_offset = 0x2c; if (read_32bitBE(stpr_offset,streamFile) == 0x53545052) { /* "STPR" */ name_offset = stpr_offset + 0xE8; /* there are offsets fields but seems to work */ } codec = ATRAC9; } else { /* apparently there is a PS3 variation (MSF inside?) */ goto fail; } /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = sample_rate; vgmstream->loop_start_sample = loop_start_sample; vgmstream->loop_end_sample = loop_end_sample; vgmstream->meta_type = meta_GTD; if (name_offset) //encoding is Shift-Jis in some PSV files read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamFile); switch(codec) { #ifdef VGM_USE_FFMPEG case XMA2: { uint8_t buf[0x100]; size_t bytes; bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile, 1); if (bytes <= 0) goto fail; vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,data_size); if ( !vgmstream->codec_data ) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; vgmstream->num_samples = num_samples; xma_fix_raw_samples(vgmstream, streamFile, start_offset, data_size, chunk_offset, 1,1); break; } #endif #ifdef VGM_USE_ATRAC9 case ATRAC9: { atrac9_config cfg = {0}; cfg.channels = vgmstream->channels; cfg.config_data = at9_config_data; vgmstream->codec_data = init_atrac9(&cfg); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_ATRAC9; vgmstream->layout_type = layout_none; if (loop_flag) { vgmstream->loop_start_sample = atrac9_bytes_to_samples(loop_start_offset - start_offset, vgmstream->codec_data); vgmstream->loop_end_sample = atrac9_bytes_to_samples(loop_end_offset - start_offset, vgmstream->codec_data); } vgmstream->num_samples = atrac9_bytes_to_samples(data_size, vgmstream->codec_data); break; } #endif default: goto fail; } /* open the file for reading */ if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* SGXD - Sony/SCEI's format (SGB+SGH / SGD / SGX) */ VGMSTREAM * init_vgmstream_sgxd(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; STREAMFILE * streamHeader = NULL; off_t start_offset, data_offset, chunk_offset, name_offset = 0; size_t stream_size; int is_sgx, is_sgb = 0; int loop_flag, channels, type; int sample_rate, num_samples, loop_start_sample, loop_end_sample; int total_subsongs, target_subsong = streamFile->stream_index; /* check extension, case insensitive */ /* .sgx: header+data (Genji), .sgd: header+data, .sgh/sgd: header/data */ if (!check_extensions(streamFile,"sgx,sgd,sgb")) goto fail; is_sgx = check_extensions(streamFile,"sgx"); is_sgb = check_extensions(streamFile,"sgb"); /* SGB+SGH: use SGH as header; otherwise use the current file as header */ if (is_sgb) { streamHeader = open_stream_ext(streamFile, "sgh"); if (!streamHeader) goto fail; } else { streamHeader = streamFile; } /* SGXD base (size 0x10) */ if (read_32bitBE(0x00,streamHeader) != 0x53475844) /* "SGXD" */ goto fail; /* 0x04 SGX: full header_size; SGD/SGH: unknown header_size (counting from 0x0/0x8/0x10, varies) */ /* 0x08 SGX: first chunk offset? (0x10); SGD/SGH: full header_size */ /* 0x0c SGX/SGH: full data size with padding; SGD: full data size + 0x80000000 with padding */ if (is_sgb) { data_offset = 0x00; } else if ( is_sgx ) { data_offset = read_32bitLE(0x04,streamHeader); } else { data_offset = read_32bitLE(0x08,streamHeader); } /* typical chunks: WAVE, RGND, NAME (strings for WAVE or RGND), SEQD (related to SFX), WSUR, WMKR, BUSS */ /* WAVE chunk (size 0x10 + files * 0x38 + optional padding) */ if (is_sgx) { /* position after chunk+size */ if (read_32bitBE(0x10,streamHeader) != 0x57415645) goto fail; /* "WAVE" */ chunk_offset = 0x18; } else { if (!find_chunk_le(streamHeader, 0x57415645,0x10,0, &chunk_offset,NULL)) goto fail; /* "WAVE" */ } /* 0x04 SGX: unknown; SGD/SGH: chunk length, 0x08 null */ /* check multi-streams (usually only SE containers; Puppeteer) */ total_subsongs = read_32bitLE(chunk_offset+0x04,streamHeader); if (target_subsong == 0) target_subsong = 1; if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; /* read stream header */ { off_t stream_offset; chunk_offset += 0x08 + 0x38 * (target_subsong-1); /* position in target header*/ /* 0x00 ? (00/01/02) */ if (!is_sgx) /* meaning unknown in .sgx; offset 0 = not a stream (a RGND sample) */ name_offset = read_32bitLE(chunk_offset+0x04,streamHeader); type = read_8bit(chunk_offset+0x08,streamHeader); channels = read_8bit(chunk_offset+0x09,streamHeader); /* 0x0a null */ sample_rate = read_32bitLE(chunk_offset+0x0c,streamHeader); /* 0x10 info_type: meaning of the next value * (00=null, 30/40=data size without padding (ADPCM, ATRAC3plus), 80/A0=block size (AC3) */ /* 0x14 info_value (see above) */ /* 0x18 unknown (ex. 0x0008/0010/3307/CC02/etc)x2 */ /* 0x1c null */ num_samples = read_32bitLE(chunk_offset+0x20,streamHeader); loop_start_sample = read_32bitLE(chunk_offset+0x24,streamHeader); loop_end_sample = read_32bitLE(chunk_offset+0x28,streamHeader); stream_size = read_32bitLE(chunk_offset+0x2c,streamHeader); /* stream size (without padding) / interleave (for type3) */ if (is_sgx) { stream_offset = 0x0; } else{ stream_offset = read_32bitLE(chunk_offset+0x30,streamHeader); } /* 0x34 SGX: unknown; SGD/SGH: stream size (with padding) / interleave */ loop_flag = loop_start_sample!=0xffffffff && loop_end_sample!=0xffffffff; start_offset = data_offset + stream_offset; } /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channels,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = sample_rate; vgmstream->num_samples = num_samples; vgmstream->loop_start_sample = loop_start_sample; vgmstream->loop_end_sample = loop_end_sample; vgmstream->num_streams = total_subsongs; vgmstream->stream_size = stream_size; vgmstream->meta_type = meta_SGXD; if (name_offset) read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamHeader); /* needs -1 to match RIFF AT3's loop chunk * (maybe SGXD = "loop before this sample" rather than "loop after this sample" as expected by vgmstream) */ if (vgmstream->loop_end_sample > 0) vgmstream->loop_end_sample -= 1; switch (type) { case 0x03: /* PS-ADPCM [Genji (PS3), Ape Escape Move (PS3)]*/ vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; if (is_sgx || is_sgb) { vgmstream->interleave_block_size = 0x10; } else { /* this only seems to happen with SFX */ vgmstream->interleave_block_size = stream_size; } break; #ifdef VGM_USE_FFMPEG case 0x04: { /* ATRAC3plus [Kurohyo 1/2 (PSP), BraveStory (PSP)] */ ffmpeg_codec_data *ffmpeg_data; /* internally has a RIFF header; but the SGXD header / sample rate has priority over it (may not match) */ ffmpeg_data = init_ffmpeg_offset(streamFile, start_offset, stream_size); if ( !ffmpeg_data ) goto fail; vgmstream->codec_data = ffmpeg_data; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; /* manually read skip_samples if FFmpeg didn't do it */ if (ffmpeg_data->skipSamples <= 0) { off_t chunk_offset; size_t chunk_size, fact_skip_samples = 0; if (!find_chunk_le(streamFile, 0x66616374,start_offset+0xc,0, &chunk_offset,&chunk_size)) /* find "fact" */ goto fail; if (chunk_size == 0x8) { fact_skip_samples = read_32bitLE(chunk_offset+0x4, streamFile); } else if (chunk_size == 0xc) { fact_skip_samples = read_32bitLE(chunk_offset+0x8, streamFile); } ffmpeg_set_skip_samples(ffmpeg_data, fact_skip_samples); } /* SGXD loop/sample values are relative (without skip samples) vs RIFF (with skip samples), no need to adjust */ break; } #endif case 0x05: /* Short PS-ADPCM [Afrika (PS3)] */ vgmstream->coding_type = coding_PSX_cfg; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x4; break; #ifdef VGM_USE_FFMPEG case 0x06: { /* AC3 [Tokyo Jungle (PS3), Afrika (PS3)] */ ffmpeg_codec_data *ffmpeg_data; ffmpeg_data = init_ffmpeg_offset(streamFile, start_offset, stream_size); if ( !ffmpeg_data ) goto fail; vgmstream->codec_data = ffmpeg_data; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; /* manually set skip_samples if FFmpeg didn't do it */ if (ffmpeg_data->skipSamples <= 0) { /* PS3 AC3 consistently has 256 encoder delay samples, and there are ~1000-2000 samples after num_samples. * Skipping them marginally improves full loops in some Tokyo Jungle tracks (ex. a_1.sgd). */ ffmpeg_set_skip_samples(ffmpeg_data, 256); } /* SGXD loop/sample values are relative (without skip samples), no need to adjust */ break; } #endif default: goto fail; } /* open the file for reading */ if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; if (is_sgb && streamHeader) close_streamfile(streamHeader); return vgmstream; fail: if (is_sgb && streamHeader) close_streamfile(streamHeader); close_vgmstream(vgmstream); return NULL; }
/* MTAF - found in Metal Gear Solid 3: Snake Eater (PS2), Subsistence and HD too */ VGMSTREAM * init_vgmstream_mtaf(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count; int32_t loop_start, loop_end; /* checks */ if ( !check_extensions(streamFile,"mtaf")) goto fail; if (read_32bitBE(0x00, streamFile) != 0x4d544146) /* "MTAF" */ goto fail; /* 0x04(4): pseudo file size (close but smaller) */ /* 0x08(4): version? (0), 0x0c(20): null, 0x30(32): some kind of id or config? */ /* HEAD chunk */ if (read_32bitBE(0x40, streamFile) != 0x48454144) /* "HEAD" */ goto fail; if (read_32bitLE(0x44, streamFile) != 0xB0) /* HEAD size */ goto fail; /* 0x48(4): null, 0x4c: usually channel count (sometimes 0x10 with 2ch), 0x50(4): 0x7F (vol?), 0x54(2): 0x40 (pan?) */ channel_count = 2 * read_8bit(0x61, streamFile); /* 0x60(4): full block size (0x110 * channels), but this works */ /* 0x70(4): ? (00/05/07), 0x80 .. 0xf8: null */ loop_start = read_32bitLE(0x58, streamFile); loop_end = read_32bitLE(0x5c, streamFile); loop_flag = read_32bitLE(0x70, streamFile) & 1; /* check loop points vs frame counts */ if (loop_start/0x100 != read_32bitLE(0x64, streamFile) || loop_end /0x100 != read_32bitLE(0x68, streamFile) ) { VGM_LOG("MTAF: wrong loop points\n"); goto fail; } /* TRKP chunks (x16) */ /* just seem to contain pan/vol stuff (0x7f/0x40), one TRKP with data per channel and the rest fixed values */ /* DATA chunk */ if (read_32bitBE(0x7f8, streamFile) != 0x44415441) /* "DATA" */ goto fail; /* 0x7fc: data size (without blocks in case of blocked layout) */ start_offset = 0x800; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count, loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = 48000; /* always */ vgmstream->num_samples = loop_end; vgmstream->loop_start_sample = loop_start; vgmstream->loop_end_sample = loop_end; vgmstream->coding_type = coding_MTAF; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x110 / 2; /* kinda hacky for MTAF (stereo codec) track layout */ vgmstream->meta_type = meta_MTAF; if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* SAB - from Worms 4: Mayhem (PC/Xbox/PS2) */ VGMSTREAM * init_vgmstream_sab(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count = 0, is_stream, align, codec, sample_rate, stream_size, loop_start, loop_end; int total_subsongs, target_subsong = streamFile->stream_index; /* .sab: main, .sob: config/names */ if (!check_extensions(streamFile,"sab")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x43535732 && /* "CSW2" (Windows) */ read_32bitBE(0x00,streamFile) != 0x43535032 && /* "CSP2" (PS2) */ read_32bitBE(0x00,streamFile) != 0x43535832) /* "CSX2" (Xbox) */ goto fail; is_stream = read_32bitLE(0x04,streamFile) & 0x04; /* other flags don't seem to matter */ total_subsongs = is_stream ? 1 : read_32bitLE(0x08,streamFile); if (target_subsong == 0) target_subsong = 1; if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; align = read_32bitLE(0x0c,streamFile); /* doubles as interleave */ /* stream config */ codec = read_32bitLE(0x18 + 0x1c*(target_subsong-1) + 0x00,streamFile); channel_count = read_32bitLE(0x18 + 0x1c*(target_subsong-1) + 0x04,streamFile); sample_rate = read_32bitLE(0x18 + 0x1c*(target_subsong-1) + 0x08,streamFile); stream_size = read_32bitLE(0x18 + 0x1c*(target_subsong-1) + 0x0c,streamFile); loop_start = read_32bitLE(0x18 + 0x1c*(target_subsong-1) + 0x10,streamFile); loop_end = read_32bitLE(0x18 + 0x1c*(target_subsong-1) + 0x14,streamFile); loop_flag = (loop_end > 0); start_offset = 0x18 + 0x1c*total_subsongs; if (start_offset % align) start_offset += align - (start_offset % align); start_offset += read_32bitLE(0x18 + 0x1c*(target_subsong-1) + 0x18,streamFile); if (is_stream) { channel_count = read_32bitLE(0x08,streamFile); /* uncommon, but non-stream stereo exists */ stream_size *= channel_count; } /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = sample_rate; vgmstream->num_streams = total_subsongs; vgmstream->stream_size = stream_size; vgmstream->meta_type = meta_SAB; switch(codec) { case 0x01: /* PC */ vgmstream->coding_type = coding_PCM16LE; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = is_stream ? align : 0x02; vgmstream->num_samples = pcm_bytes_to_samples(stream_size, vgmstream->channels, 16); vgmstream->loop_start_sample = pcm_bytes_to_samples(loop_start, vgmstream->channels, 16); vgmstream->loop_end_sample = pcm_bytes_to_samples(loop_end, vgmstream->channels, 16); break; case 0x04: /* PS2 */ vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = is_stream ? align : 0x10; vgmstream->num_samples = ps_bytes_to_samples(stream_size, vgmstream->channels); vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, vgmstream->channels); vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, vgmstream->channels); break; case 0x08: /* Xbox */ vgmstream->coding_type = is_stream ? coding_XBOX_IMA_int : coding_XBOX_IMA; vgmstream->layout_type = is_stream ? layout_interleave : layout_none; vgmstream->interleave_block_size = is_stream ? align : 0x00; vgmstream->num_samples = xbox_ima_bytes_to_samples(stream_size, vgmstream->channels); vgmstream->loop_start_sample = xbox_ima_bytes_to_samples(loop_start, vgmstream->channels); vgmstream->loop_end_sample = xbox_ima_bytes_to_samples(loop_end, vgmstream->channels); break; default: VGM_LOG("SAB: unknown codec\n"); goto fail; } get_stream_name(vgmstream->stream_name, streamFile, target_subsong); if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* SXD - Sony/SCE's SNDX lib format (cousin of SGXD) [Gravity Rush, Freedom Wars, Soul Sacrifice PSV] */ VGMSTREAM * init_vgmstream_sxd(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; STREAMFILE * streamHeader = NULL, *streamData = NULL; off_t start_offset, chunk_offset, first_offset = 0x60, name_offset = 0; size_t chunk_size, stream_size = 0; int is_dual = 0, is_external = 0; int loop_flag, channels, codec, flags; int sample_rate, num_samples, loop_start_sample, loop_end_sample; uint32_t at9_config_data = 0; int total_subsongs, target_subsong = streamFile->stream_index; /* check extension, case insensitive */ /* .sxd: header+data (SXDF), .sxd1: header (SXDF) + .sxd2 = data (SXDS) */ if (!check_extensions(streamFile,"sxd,sxd2")) goto fail; is_dual = !check_extensions(streamFile,"sxd"); /* sxd1+sxd2: use sxd1 as header; otherwise use the current file as header */ if (is_dual) { if (read_32bitBE(0x00,streamFile) != 0x53584453) /* "SXDS" */ goto fail; streamHeader = open_streamfile_by_ext(streamFile, "sxd1"); if (!streamHeader) goto fail; } else { streamHeader = streamFile; } if (read_32bitBE(0x00,streamHeader) != 0x53584446) /* "SXDF" */ goto fail; /* typical chunks: NAME, WAVE and many control chunks (SXDs don't need to contain any sound data) */ if (!find_chunk_le(streamHeader, 0x57415645,first_offset,0, &chunk_offset,&chunk_size)) /* "WAVE" */ goto fail; /* check multi-streams (usually only in SFX containers) */ total_subsongs = read_32bitLE(chunk_offset+0x04,streamHeader); if (target_subsong == 0) target_subsong = 1; if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; // todo rarely a WAVE subsong may point to a repeated data offset, with different tags only /* read stream header */ { off_t table_offset, header_offset, stream_offset; /* get target offset using table of relative offsets within WAVE */ table_offset = chunk_offset + 0x08 + 4*(target_subsong-1); header_offset = table_offset + read_32bitLE(table_offset,streamHeader); flags = read_32bitLE(header_offset+0x00,streamHeader); codec = read_8bit (header_offset+0x04,streamHeader); channels = read_8bit (header_offset+0x05,streamHeader); /* 0x06(2): unknown, rarely 0xFF */ sample_rate = read_32bitLE(header_offset+0x08,streamHeader); /* 0x0c(4): unknown size? (0x4000/0x3999/0x3333/etc, not related to extra data) */ /* 0x10(4): ? + volume? + pan? (can be 0 for music) */ num_samples = read_32bitLE(header_offset+0x14,streamHeader); loop_start_sample = read_32bitLE(header_offset+0x18,streamHeader); loop_end_sample = read_32bitLE(header_offset+0x1c,streamHeader); stream_size = read_32bitLE(header_offset+0x20,streamHeader); stream_offset = read_32bitLE(header_offset+0x24,streamHeader); loop_flag = loop_start_sample != -1 && loop_end_sample != -1; /* known flag combos: * 0x00: Chaos Rings 2 sfx (RAM + no tags) * 0x01: common (RAM + tags) * 0x02: Chaos Rings 3 sfx (stream + no tags) * 0x03: common (stream + tags) * 0x05: Gravity Rush 2 sfx (RAM + tags) */ //has_tags = flags & 1; is_external = flags & 2; //unknown = flags & 4; /* no apparent differences with/without it? */ /* flag 1 signals TLV-like extra data. Format appears to be 0x00(1)=tag?, 0x01(1)=extra size*32b?, 0x02(2)=config? * but not always (ex. size=0x07 but actually=0), perhaps only some bits are used or varies with tag, or are subflags. * A tag may appear with or without extra data (even 0x0a), 0x01/03/04/06 are common at the beginnig (imply number of tags?), * 0x64/7F are common at the end (but not always), 0x0A=ATRAC9 config, 0x0B/0C appear with RAM preloading data * (most variable in Soul Sacrifice; total TLVs size isn't plainly found in the SXD header AFAIK). */ /* manually try to find ATRAC9 tag */ if (codec == 0x42) { off_t extra_offset = header_offset+0x28; off_t max_offset = chunk_offset + chunk_size; if (!(flags & 1)) goto fail; while (extra_offset < max_offset) { uint32_t tag = read_32bitBE(extra_offset, streamHeader); if (tag == 0x0A010000 || tag == 0x0A010600) { at9_config_data = read_32bitLE(extra_offset+0x04,streamHeader); /* yes, LE */ break; } extra_offset += 0x04; } if (!at9_config_data) goto fail; } /* usually .sxd=header+data and .sxd1=header + .sxd2=data, but rarely sxd1 may contain data [The Last Guardian (PS4)] */ if (is_external) { start_offset = stream_offset; /* absolute if external */ } else { start_offset = header_offset+0x24 + stream_offset; /* from current entry offset if internal */ } } /* get stream name (NAME is tied to REQD/cues, and SFX cues repeat WAVEs, but should work ok for streams) */ if (is_dual && find_chunk_le(streamHeader, 0x4E414D45,first_offset,0, &chunk_offset,NULL)) { /* "NAME" */ /* table: relative offset (32b) + hash? (32b) + cue index (32b) */ int i; int num_entries = read_16bitLE(chunk_offset+0x04,streamHeader); /* can be bigger than streams */ for (i = 0; i < num_entries; i++) { uint32_t index = (uint32_t)read_32bitLE(chunk_offset+0x08 + 0x08 + i*0x0c,streamHeader); if (index+1 == target_subsong) { name_offset = chunk_offset+0x08 + 0x00 + i*0x0c + read_32bitLE(chunk_offset+0x08 + 0x00 + i*0x0c,streamHeader); break; } } } if (is_external && !is_dual) { VGM_LOG("SXD: found single sxd with external data\n"); goto fail; } if (is_external) { streamData = streamFile; } else { streamData = streamHeader; } if (start_offset > get_streamfile_size(streamData)) { VGM_LOG("SXD: wrong location?\n"); goto fail; } /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channels,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = sample_rate; vgmstream->num_samples = num_samples; vgmstream->loop_start_sample = loop_start_sample; vgmstream->loop_end_sample = loop_end_sample; vgmstream->num_streams = total_subsongs; vgmstream->stream_size = stream_size; vgmstream->meta_type = meta_SXD; if (name_offset) read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamHeader); switch (codec) { case 0x20: /* PS-ADPCM [Hot Shots Golf: World Invitational (Vita) sfx] */ vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x10; break; case 0x21: /* HEVAG [Gravity Rush (Vita) sfx] */ vgmstream->coding_type = coding_HEVAG; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x10; break; #ifdef VGM_USE_ATRAC9 case 0x42: { /* ATRAC9 [Soul Sacrifice (Vita), Freedom Wars (Vita), Gravity Rush 2 (PS4)] */ atrac9_config cfg = {0}; cfg.channels = vgmstream->channels; cfg.config_data = at9_config_data; vgmstream->codec_data = init_atrac9(&cfg); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_ATRAC9; vgmstream->layout_type = layout_none; break; } #endif //case 0x28: /* dummy codec? (found with 0 samples) [Hot Shots Golf: World Invitational (Vita) sfx] */ default: VGM_LOG("SXD: unknown codec 0x%x\n", codec); goto fail; } /* open the file for reading */ if (!vgmstream_open_stream(vgmstream,streamData,start_offset)) goto fail; if (is_dual) close_streamfile(streamHeader); return vgmstream; fail: if (is_dual) close_streamfile(streamHeader); close_vgmstream(vgmstream); return NULL; }
/* .ADS - Sony's "Audio Stream" format [Edit Racing (PS2), Evergrace II (PS2), Pri-Saga! Portable (PSP)] */ VGMSTREAM * init_vgmstream_ps2_ads(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count, sample_rate, interleave, is_loop_samples = 0; size_t body_size, stream_size, file_size; uint32_t codec, loop_start_sample = 0, loop_end_sample = 0, loop_start_offset = 0, loop_end_offset = 0; coding_t coding_type; int ignore_silent_frame_cavia = 0, ignore_silent_frame_capcom = 0; /* checks */ /* .ads: actual extension * .ss2: demuxed videos (fake?) * .pcm: Taisho Mononoke Ibunroku (PS2) * .adx: Armored Core 3 (PS2) * [no actual extension]: MotoGP (PS2) * .800: Mobile Suit Gundam: The One Year War (PS2) */ if (!check_extensions(streamFile, "ads,ss2,pcm,adx,,800")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x53536864 && /* "SShd" */ read_32bitBE(0x20,streamFile) != 0x53536264) /* "SSbd" */ goto fail; if (read_32bitLE(0x04,streamFile) != 0x18 && /* standard header size */ read_32bitLE(0x04,streamFile) != 0x20) /* True Fortune (PS2) */ goto fail; /* base values (a bit unorderly since devs hack ADS too much and detection is messy) */ { codec = read_32bitLE(0x08,streamFile); sample_rate = read_32bitLE(0x0C,streamFile); channel_count = read_32bitLE(0x10,streamFile); /* up to 4 [Eve of Extinction (PS2)]*/ interleave = read_32bitLE(0x14,streamFile); /* set even when mono */ switch(codec) { case 0x01: /* official definition */ case 0x80000001: /* [Evergrace II (PS2), but not other From Soft games] */ coding_type = coding_PCM16LE; /* Angel Studios/Rockstar San Diego videos codec hijack [Red Dead Revolver (PS2), Spy Hunter 2 (PS2)] */ if (sample_rate == 12000 && interleave == 0x200) { sample_rate = 48000; interleave = 0x40; coding_type = coding_DVI_IMA_int; /* should try to detect IMA data but it's not so easy, this works ok since * no known games use these settings, videos normally are 48000/24000hz */ } break; case 0x10: /* official definition */ case 0x02: /* Capcom games extension, stereo only [Megaman X7 (PS2), Breath of Fire V (PS2), Clock Tower 3 (PS2)] */ coding_type = coding_PSX; break; case 0x00: /* PCM16BE from official docs, probably never used */ default: VGM_LOG("ADS: unknown codec\n"); goto fail; } } /* sizes */ { file_size = get_streamfile_size(streamFile); body_size = read_32bitLE(0x24,streamFile); /* bigger than file_size in rare cases, even if containing all data (ex. Megaman X7's SY04.ADS) */ if (body_size + 0x28 > file_size) { body_size = file_size - 0x28; } /* True Fortune: weird stream size */ if (body_size * 2 == file_size - 0x18) { body_size = (body_size * 2) - 0x10; } stream_size = body_size; } /* offset */ { start_offset = 0x28; /* start padding (body size is ok, may have end padding) [Evergrace II (PS2), Armored Core 3 (PS2)] */ /* detection depends on files being properly ripped, so broken/cut files won't play ok */ if (file_size - body_size >= 0x800) { start_offset = 0x800; /* aligned to sector */ /* too much end padding, happens in Super Galdelic Hour's SEL.ADS, maybe in bad rips too */ VGM_ASSERT(file_size - body_size > 0x8000, "ADS: big end padding %x\n", file_size - body_size); } /* "ADSC" container */ if (coding_type == coding_PSX && read_32bitLE(0x28,streamFile) == 0x1000 /* real start */ && read_32bitLE(0x2c,streamFile) == 0 && read_32bitLE(0x1008,streamFile) != 0) { int i; int is_adsc = 1; /* should be empty up to data start */ for (i = 0; i < 0xFDC/4; i++) { if (read_32bitLE(0x2c+(i*4),streamFile) != 0) { is_adsc = 0; break; } } if (is_adsc) { start_offset = 0x1000 - 0x08; /* remove "ADSC" alignment */ /* stream_size doesn't count start offset padding */ } } } /* loops */ { uint32_t loop_start, loop_end; loop_start = read_32bitLE(0x18,streamFile); loop_end = read_32bitLE(0x1C,streamFile); loop_flag = 0; /* detect loops the best we can; docs say those are loop block addresses, * but each maker does whatever (no games seem to use PS-ADPCM loop flags though) */ if (loop_start != 0xFFFFFFFF && loop_end == 0xFFFFFFFF) { if (codec == 0x02) { /* Capcom codec */ /* Capcom games: loop_start is address * 0x10 [Mega Man X7, Breath of Fire V, Clock Tower 3] */ loop_flag = ((loop_start * 0x10) + 0x200 < body_size); /* near the end (+0x20~80) means no loop */ loop_start_offset = loop_start * 0x10; ignore_silent_frame_capcom = 1; } else if (read_32bitBE(0x28,streamFile) == 0x50414421) { /* "PAD!" padding until 0x800 */ /* Super Galdelic Hour: loop_start is PCM bytes */ loop_flag = 1; loop_start_sample = loop_start / 2 / channel_count; is_loop_samples = 1; } else if ((loop_start % 0x800 == 0) && loop_start > 0) {/* sector-aligned, min/0 is 0x800 */ /* cavia games: loop_start is offset [Drakengard 1/2, GITS: Stand Alone Complex] */ /* offset is absolute from the "cavia stream format" container that adjusts ADS start */ loop_flag = 1; loop_start_offset = loop_start - 0x800; ignore_silent_frame_cavia = 1; } else if (loop_start % 0x800 != 0 || loop_start == 0) { /* not sector aligned */ /* Katakamuna: loop_start is address * 0x10 */ loop_flag = 1; loop_start_offset = loop_start * 0x10; } } else if (loop_start != 0xFFFFFFFF && loop_end != 0xFFFFFFFF && loop_end > 0) { /* ignore Kamen Rider Blade and others */ #if 0 //todo improve detection to avoid clashing with address*0x20 if (loop_end == body_size / 0x10) { /* always body_size? but not all files should loop */ /* Akane Iro ni Somaru Saka - Parallel: loops is address * 0x10 */ loop_flag = 1; loop_start_offset = loop_start * 0x10; loop_end_offset = loop_end * 0x10; } #endif if (loop_end <= body_size / 0x70 && coding_type == coding_PCM16LE) { /* close to body_size */ /* Armored Core - Nexus: loops is address * 0x70 */ loop_flag = 1; loop_start_offset = loop_start * 0x70; loop_end_offset = loop_end * 0x70; } else if (loop_end <= body_size / 0x20 && coding_type == coding_PCM16LE) { /* close to body_size */ /* Armored Core - Nine Breaker: loops is address * 0x20 */ loop_flag = 1; loop_start_offset = loop_start * 0x20; loop_end_offset = loop_end * 0x20; } else if (loop_end <= body_size / 0x20 && coding_type == coding_PSX) { /* close to body_size */ /* various games: loops is address * 0x20 [Fire Pro Wrestling Returns, A.C.E. - Another Century's Episode] */ loop_flag = 1; loop_start_offset = loop_start * 0x20; loop_end_offset = loop_end * 0x20; } else if ((loop_end > body_size / 0x20 && coding_type == coding_PSX) || (loop_end > body_size / 0x70 && coding_type == coding_PCM16LE)) { /* various games: loops in samples [Eve of Extinction, Culdcept, WWE Smackdown! 3] */ loop_flag = 1; loop_start_sample = loop_start; loop_end_sample = loop_end; is_loop_samples = 1; } } //todo Jet Ion Grand Prix seems to have some loop-like values at 0x28 //todo Yoake mae yori Ruriiro na has loops in unknown format } /* most games have empty PS-ADPCM frames in the last interleave block that should be skipped for smooth looping */ if (coding_type == coding_PSX) { off_t offset, min_offset; offset = start_offset + stream_size; min_offset = offset - interleave; do { offset -= 0x10; if (read_8bit(offset+0x01,streamFile) == 0x07) { stream_size -= 0x10*channel_count;/* ignore don't decode flag/padding frame (most common) [ex. Capcom games] */ } else if (read_32bitBE(offset+0x00,streamFile) == 0x00000000 && read_32bitBE(offset+0x04,streamFile) == 0x00000000 && read_32bitBE(offset+0x08,streamFile) == 0x00000000 && read_32bitBE(offset+0x0c,streamFile) == 0x00000000) { stream_size -= 0x10*channel_count; /* ignore null frame [ex. A.C.E. Another Century Episode 1/2/3] */ } else if (read_32bitBE(offset+0x00,streamFile) == 0x00007777 && read_32bitBE(offset+0x04,streamFile) == 0x77777777 && read_32bitBE(offset+0x08,streamFile) == 0x77777777 && read_32bitBE(offset+0x0c,streamFile) == 0x77777777) { stream_size -= 0x10*channel_count; /* ignore padding frame [ex. Akane Iro ni Somaru Saka - Parallel] */ } else if (read_32bitBE(offset+0x00,streamFile) == 0x0C020000 && read_32bitBE(offset+0x04,streamFile) == 0x00000000 && read_32bitBE(offset+0x08,streamFile) == 0x00000000 && read_32bitBE(offset+0x0c,streamFile) == 0x00000000 && ignore_silent_frame_cavia) { stream_size -= 0x10*channel_count; /* ignore silent frame [ex. cavia games] */ } else if (read_32bitBE(offset+0x00,streamFile) == 0x0C010000 && read_32bitBE(offset+0x04,streamFile) == 0x00000000 && read_32bitBE(offset+0x08,streamFile) == 0x00000000 && read_32bitBE(offset+0x0c,streamFile) == 0x00000000 && ignore_silent_frame_capcom) { stream_size -= 0x10*channel_count; /* ignore silent frame [ex. Capcom games] */ } else { break; /* standard frame */ } } while(offset > min_offset); /* don't bother fixing loop_end_offset since will be adjusted to num_samples later, if needed */ } /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = sample_rate; vgmstream->coding_type = coding_type; vgmstream->interleave_block_size = interleave; vgmstream->layout_type = layout_interleave; vgmstream->meta_type = meta_PS2_SShd; switch(coding_type) { case coding_PCM16LE: vgmstream->num_samples = pcm_bytes_to_samples(stream_size, channel_count, 16); break; case coding_PSX: vgmstream->num_samples = ps_bytes_to_samples(stream_size, channel_count); break; case coding_DVI_IMA_int: vgmstream->num_samples = ima_bytes_to_samples(stream_size, channel_count); break; default: goto fail; } if (vgmstream->loop_flag) { if (is_loop_samples) { vgmstream->loop_start_sample = loop_start_sample; vgmstream->loop_end_sample = loop_end_sample; } else { switch(vgmstream->coding_type) { case coding_PCM16LE: vgmstream->loop_start_sample = pcm_bytes_to_samples(loop_start_offset,channel_count,16); vgmstream->loop_end_sample = pcm_bytes_to_samples(loop_end_offset,channel_count,16); break; case coding_PSX: vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start_offset,channel_count); vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end_offset,channel_count); break; default: goto fail; } } /* when loop_end = 0xFFFFFFFF */ if (vgmstream->loop_end_sample == 0) vgmstream->loop_end_sample = vgmstream->num_samples; /* happens even when loops are directly samples, loops sound fine (ex. Culdcept) */ if (vgmstream->loop_end_sample > vgmstream->num_samples) vgmstream->loop_end_sample = vgmstream->num_samples; } if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }