/* Decodes samples for segmented streams. * Chains together sequential vgmstreams, for data divided into separate sections or files * (like one part for intro and other for loop segments, which may even use different codecs). */ void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { int samples_written = 0; segmented_layout_data *data = vgmstream->layout_data; while (samples_written < sample_count) { int samples_to_do; int samples_this_block = data->segments[data->current_segment]->num_samples; if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) { /* handle looping, finding loop segment */ int loop_segment = 0, samples = 0, loop_samples_skip = 0; while (samples < vgmstream->num_samples) { int32_t segment_samples = data->segments[loop_segment]->num_samples; if (vgmstream->loop_start_sample >= samples && vgmstream->loop_start_sample < samples + segment_samples) { loop_samples_skip = vgmstream->loop_start_sample - samples; break; /* loop_start falls within loop_segment's samples */ } samples += segment_samples; loop_segment++; } if (loop_segment == data->segment_count) { VGM_LOG("segmented_layout: can't find loop segment\n"); loop_segment = 0; } if (loop_samples_skip > 0) { VGM_LOG("segmented_layout: loop starts after %i samples\n", loop_samples_skip); //todo skip/fix, but probably won't happen } data->current_segment = loop_segment; reset_vgmstream(data->segments[data->current_segment]); vgmstream->samples_into_block = 0; continue; } samples_to_do = vgmstream_samples_to_do(samples_this_block, sample_count, vgmstream); if (samples_to_do > sample_count - samples_written) samples_to_do = sample_count - samples_written; /* detect segment change and restart */ if (samples_to_do == 0) { data->current_segment++; reset_vgmstream(data->segments[data->current_segment]); vgmstream->samples_into_block = 0; continue; } render_vgmstream(&buffer[samples_written*data->segments[data->current_segment]->channels], samples_to_do,data->segments[data->current_segment]); samples_written += samples_to_do; vgmstream->current_sample += samples_to_do; vgmstream->samples_into_block += samples_to_do; } }
/* debug util, mainly for custom IO testing */ void dump_streamfile(STREAMFILE *streamFile, const char* out) { #ifdef VGM_DEBUG_OUTPUT off_t offset = 0; FILE *f = NULL; if (out) { f = fopen(out,"wb"); if (!f) return; } VGM_LOG("dump streamfile, size: %x\n", get_streamfile_size(streamFile)); while (offset < get_streamfile_size(streamFile)) { uint8_t buffer[0x8000]; size_t read; read = read_streamfile(buffer,offset,0x8000,streamFile); if (out) fwrite(buffer,sizeof(uint8_t),read, f); else VGM_LOGB(buffer,read,0); offset += read; } if (out) { fclose(f); } #endif }
int setup_layout_segmented(segmented_layout_data* data) { int i; /* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */ for (i = 0; i < data->segment_count; i++) { if (!data->segments[i]) goto fail; if (data->segments[i]->num_samples <= 0) goto fail; /* shouldn't happen */ if (data->segments[i]->loop_flag != 0) { VGM_LOG("segmented layout: segment %i is looped\n", i); data->segments[i]->loop_flag = 0; } if (i > 0) { if (data->segments[i]->channels != data->segments[i-1]->channels) goto fail; /* a bit weird, but no matter */ if (data->segments[i]->sample_rate != data->segments[i-1]->sample_rate) { VGM_LOG("segmented layout: segment %i has different sample rate\n", i); } //if (data->segments[i]->coding_type != data->segments[i-1]->coding_type) // goto fail; /* perfectly acceptable */ } /* save start things so we can restart for seeking/looping */ memcpy(data->segments[i]->start_ch,data->segments[i]->ch,sizeof(VGMSTREAMCHANNEL)*data->segments[i]->channels); memcpy(data->segments[i]->start_vgmstream,data->segments[i],sizeof(VGMSTREAM)); } return 1; fail: return 0; /* caller is expected to free */ }
static STREAMFILE * open_stdio_streamfile_buffer_by_file(FILE *infile,const char * const filename, size_t buffersize) { uint8_t * buffer = NULL; STDIOSTREAMFILE * streamfile = NULL; buffer = calloc(buffersize,1); if (!buffer) goto fail; streamfile = calloc(1,sizeof(STDIOSTREAMFILE)); if (!streamfile) goto fail; streamfile->sf.read = (void*)read_stdio; streamfile->sf.get_size = (void*)get_size_stdio; streamfile->sf.get_offset = (void*)get_offset_stdio; streamfile->sf.get_name = (void*)get_name_stdio; streamfile->sf.open = (void*)open_stdio; streamfile->sf.close = (void*)close_stdio; streamfile->infile = infile; streamfile->buffersize = buffersize; streamfile->buffer = buffer; strncpy(streamfile->name,filename,sizeof(streamfile->name)); streamfile->name[sizeof(streamfile->name)-1] = '\0'; /* cache filesize */ fseeko(streamfile->infile,0,SEEK_END); streamfile->filesize = ftello(streamfile->infile); /* Typically fseek(o)/ftell(o) may only handle up to ~2.14GB, signed 32b = 0x7FFFFFFF * (happens in banks like FSB, though rarely). Can be remedied with the * preprocessor (-D_FILE_OFFSET_BITS=64 in GCC) but it's not well tested. */ if (streamfile->filesize == 0xFFFFFFFF) { /* -1 on error */ VGM_LOG("STREAMFILE: ftell error\n"); goto fail; /* can be ignored but may result in strange/unexpected behaviors */ } return &streamfile->sf; fail: free(buffer); free(streamfile); 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; }
/* 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; }
/* 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; }
/* 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; }
/* .dsp w/ Cstr header, seen in Star Fox Assault and Donkey Konga */ VGMSTREAM * init_vgmstream_cstr(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; char filename[PATH_LIMIT]; int loop_flag; off_t start_offset; off_t first_data; off_t loop_offset; size_t interleave; int loop_adjust; int double_loop_end = 0; /* check extension, case insensitive */ streamFile->get_name(streamFile,filename,sizeof(filename)); if (strcasecmp("dsp",filename_extension(filename))) goto fail; /* check header */ if ((uint32_t)read_32bitBE(0,streamFile)!=0x43737472) /* "Cstr" */ goto fail; #ifdef DEBUG fprintf(stderr,"header ok\n"); #endif if (read_8bit(0x1b,streamFile)==1) { /* mono version, much simpler to handle */ /* Only seen in R Racing Evolution radio sfx */ start_offset = 0x80; loop_flag = read_16bitBE(0x2c,streamFile); /* check initial predictor/scale */ if (read_16bitBE(0x5e,streamFile) != (uint8_t)read_8bit(start_offset,streamFile)) goto fail; /* check type==0 and gain==0 */ if (read_16bitBE(0x2e,streamFile) || read_16bitBE(0x5c,streamFile)) goto fail; loop_offset = start_offset+read_32bitBE(0x10,streamFile); if (loop_flag) { if (read_16bitBE(0x64,streamFile) != (uint8_t)read_8bit(loop_offset,streamFile)) goto fail; } /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(1,loop_flag); if (!vgmstream) goto fail; /* fill in the vital statistics */ vgmstream->sample_rate = read_32bitBE(0x28,streamFile); vgmstream->num_samples = read_32bitBE(0x20,streamFile); if (loop_flag) { vgmstream->loop_start_sample = dsp_nibbles_to_samples( read_32bitBE(0x30,streamFile)); vgmstream->loop_end_sample = dsp_nibbles_to_samples( read_32bitBE(0x34,streamFile))+1; } vgmstream->coding_type = coding_NGC_DSP; vgmstream->layout_type = layout_none; vgmstream->meta_type = meta_DSP_CSTR; { int i; for (i=0;i<16;i++) vgmstream->ch[0].adpcm_coef[i]=read_16bitBE(0x3c+i*2,streamFile); } /* open the file for reading by each channel */ vgmstream->ch[0].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); if (!vgmstream->ch[0].streamfile) goto fail; vgmstream->ch[0].channel_start_offset= vgmstream->ch[0].offset= start_offset; return vgmstream; } /* end mono */ interleave = read_16bitBE(0x06,streamFile); start_offset = 0xe0; first_data = start_offset+read_32bitBE(0x0c,streamFile); loop_flag = read_16bitBE(0x2c,streamFile); if (!loop_flag) { /* Nonlooped tracks seem to follow no discernable pattern * with where they actually start. * But! with the magic of initial p/s redundancy, we can guess. */ while (first_data<start_offset+0x800 && (read_16bitBE(0x5e,streamFile) != (uint8_t)read_8bit(first_data,streamFile) || read_16bitBE(0xbe,streamFile) != (uint8_t)read_8bit(first_data+interleave,streamFile))) first_data+=8; #ifdef DEBUG fprintf(stderr,"guessed first_data at %#x\n",first_data); #endif } /* check initial predictor/scale */ if (read_16bitBE(0x5e,streamFile) != (uint8_t)read_8bit(first_data,streamFile)) goto fail; if (read_16bitBE(0xbe,streamFile) != (uint8_t)read_8bit(first_data+interleave,streamFile)) goto fail; #ifdef DEBUG fprintf(stderr,"p/s ok\n"); #endif /* check type==0 and gain==0 */ if (read_16bitBE(0x2e,streamFile) || read_16bitBE(0x5c,streamFile)) goto fail; if (read_16bitBE(0x8e,streamFile) || read_16bitBE(0xbc,streamFile)) goto fail; #ifdef DEBUG fprintf(stderr,"type & gain ok\n"); #endif /* check for loop flag agreement */ if (read_16bitBE(0x2c,streamFile) != read_16bitBE(0x8c,streamFile)) goto fail; #ifdef DEBUG fprintf(stderr,"loop flags agree\n"); #endif loop_offset = start_offset+read_32bitBE(0x10,streamFile)*2; if (loop_flag) { int loops_ok=0; /* check loop predictor/scale */ /* some fuzz allowed */ for (loop_adjust=0;loop_adjust>=-0x10;loop_adjust-=8) { #ifdef DEBUG fprintf(stderr,"looking for loop p/s at %#x,%#x\n",loop_offset-interleave+loop_adjust,loop_offset+loop_adjust); #endif if (read_16bitBE(0x64,streamFile) == (uint8_t)read_8bit(loop_offset-interleave+loop_adjust,streamFile) && read_16bitBE(0xc4,streamFile) == (uint8_t)read_8bit(loop_offset+loop_adjust,streamFile)) { loops_ok=1; break; } } if (!loops_ok) for (loop_adjust=interleave;loop_adjust<=interleave+0x10;loop_adjust+=8) { #ifdef DEBUG fprintf(stderr,"looking for loop p/s at %#x,%#x\n",loop_offset-interleave+loop_adjust,loop_offset+loop_adjust); #endif if (read_16bitBE(0x64,streamFile) == (uint8_t)read_8bit(loop_offset-interleave+loop_adjust,streamFile) && read_16bitBE(0xc4,streamFile) == (uint8_t)read_8bit(loop_offset+loop_adjust,streamFile)) { loops_ok=1; break; } } if (!loops_ok) goto fail; #ifdef DEBUG fprintf(stderr,"loop p/s ok (with %#4x adjust)\n",loop_adjust); #endif /* check for agreement */ /* loop end (channel 1 & 2 headers) */ if (read_32bitBE(0x34,streamFile) != read_32bitBE(0x94,streamFile)) goto fail; /* Mr. Driller oddity */ if (dsp_nibbles_to_samples(read_32bitBE(0x34,streamFile)*2)+1 <= read_32bitBE(0x20,streamFile)) { #ifdef DEBUG fprintf(stderr,"loop end <= half total samples, should be doubled\n"); #endif double_loop_end = 1; } /* loop start (Cstr header and channel 1 header) */ if (read_32bitBE(0x30,streamFile) != read_32bitBE(0x10,streamFile) #if 0 /* this particular glitch only true for SFA, though it * seems like something similar happens in Donkey Konga */ /* loop start (Cstr, channel 1 & 2 headers) */ || (read_32bitBE(0x0c,streamFile)+read_32bitLE(0x30,streamFile)) != read_32bitBE(0x90,streamFile) #endif ) /* alternatively (Donkey Konga) the header loop is 0x0c+0x10 */ if ( /* loop start (Cstr header and channel 1 header) */ read_32bitBE(0x30,streamFile) != read_32bitBE(0x10,streamFile)+ read_32bitBE(0x0c,streamFile)) /* further alternatively (Donkey Konga), if we loop back to * the very first frame 0x30 might be 0x00000002 (which * is a *valid* std dsp loop start, imagine that) while 0x10 * is 0x00000000 */ if (!(read_32bitBE(0x30,streamFile) == 2 && read_32bitBE(0x10,streamFile) == 0)) /* lest there be too few alternatives, in Mr. Driller we * find that [0x30] + [0x0c] + 8 = [0x10]*2 */ if (!(double_loop_end && read_32bitBE(0x30,streamFile) + read_32bitBE(0x0c,streamFile) + 8 == read_32bitBE(0x10,streamFile)*2)) { /* for Mr.Driller Drill Land, no idea about the above but seems to play and loop ok */ VGM_LOG("Cstr: bad loop check ignored\n"); //goto fail; } #ifdef DEBUG fprintf(stderr,"loop points agree\n"); #endif } /* assure that sample counts, sample rates agree */ if ( /* sample count (channel 1 & 2 headers) */ read_32bitBE(0x20,streamFile) != read_32bitBE(0x80,streamFile) || /* sample rate (channel 1 & 2 headers) */ read_32bitBE(0x28,streamFile) != read_32bitBE(0x88,streamFile) || /* sample count (Cstr header and channel 1 header) */ read_32bitLE(0x14,streamFile) != read_32bitBE(0x20,streamFile) || /* sample rate (Cstr header and channel 1 header) */ (uint16_t)read_16bitLE(0x18,streamFile) != read_32bitBE(0x28,streamFile)) goto fail; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(2,loop_flag); if (!vgmstream) goto fail; /* fill in the vital statistics */ vgmstream->sample_rate = read_32bitBE(0x28,streamFile); /* This is a slight hack to counteract their hack. * All the data is ofset by first_data so that the loop * point occurs at a block boundary. However, I always begin decoding * right after the header, as that is the start of the first block and * my interleave code relies on starting at the beginning of a block. * So we decode a few silent samples at the beginning, and here we make up * for it by lengthening the track by that much. */ vgmstream->num_samples = read_32bitBE(0x20,streamFile) + (first_data-start_offset)/8*14; if (loop_flag) { off_t loop_start_bytes = loop_offset-start_offset-interleave; vgmstream->loop_start_sample = dsp_nibbles_to_samples((loop_start_bytes/(2*interleave)*interleave+loop_start_bytes%(interleave*2))*2); /*dsp_nibbles_to_samples(loop_start_bytes);*/ /*dsp_nibbles_to_samples(read_32bitBE(0x30,streamFile)*2-inter);*/ vgmstream->loop_end_sample = dsp_nibbles_to_samples( read_32bitBE(0x34,streamFile))+1; if (double_loop_end) vgmstream->loop_end_sample = dsp_nibbles_to_samples(read_32bitBE(0x34,streamFile)*2)+1; if (vgmstream->loop_end_sample > vgmstream->num_samples) { #ifdef DEBUG fprintf(stderr,"loop_end_sample > num_samples, adjusting\n"); #endif vgmstream->loop_end_sample = vgmstream->num_samples; } } vgmstream->coding_type = coding_NGC_DSP; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = interleave; vgmstream->meta_type = meta_DSP_CSTR; { int i; for (i=0;i<16;i++) vgmstream->ch[0].adpcm_coef[i]=read_16bitBE(0x3c+i*2,streamFile); for (i=0;i<16;i++) vgmstream->ch[1].adpcm_coef[i]=read_16bitBE(0x9c+i*2,streamFile); } /* open the file for reading by each channel */ { int i; for (i=0;i<2;i++) { vgmstream->ch[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); if (!vgmstream->ch[i].streamfile) goto fail; vgmstream->ch[i].channel_start_offset= vgmstream->ch[i].offset= start_offset+interleave*i; } } return vgmstream; /* clean up anything we may have opened */ fail: if (vgmstream) 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; }
/* .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; }
/* Convers custom Opus packets to Ogg Opus, so the resulting data is larger than physical data. */ static size_t opus_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, opus_io_data* data) { size_t total_read = 0; /* ignore bad reads */ if (offset < 0 || offset > data->logical_size) { return total_read; } /* previous offset: re-start as we can't map logical<>physical offsets */ if (offset < data->logical_offset) { data->physical_offset = data->stream_offset; data->logical_offset = 0x00; data->page_size = 0; data->samples_done = 0; data->sequence = 2; /* appended header is 0/1 */ if (offset >= data->head_size) data->logical_offset = data->head_size; } /* insert fake header */ if (offset < data->head_size) { size_t bytes_consumed, to_read; bytes_consumed = offset - data->logical_offset; to_read = data->head_size - bytes_consumed; if (to_read > length) to_read = length; memcpy(dest, data->head_buffer + bytes_consumed, to_read); total_read += to_read; dest += to_read; offset += to_read; length -= to_read; data->logical_offset += to_read; } /* read blocks, one at a time */ while (length > 0) { /* ignore EOF */ if (data->logical_offset >= data->logical_size) { break; } /* process new block */ if (data->page_size == 0) { size_t data_size, skip_size, oggs_size; switch(data->type) { case OPUS_SWITCH: /* format seem to come from opus_test and not Nintendo-specific */ data_size = read_32bitBE(data->physical_offset, streamfile); skip_size = 0x08; /* size + Opus state(?) */ break; case OPUS_UE4: data_size = (uint16_t)read_16bitLE(data->physical_offset, streamfile); skip_size = 0x02; break; case OPUS_EA: data_size = (uint16_t)read_16bitBE(data->physical_offset, streamfile); skip_size = 0x02; break; case OPUS_X: data_size = get_xopus_packet_size(data->sequence - 2, streamfile); skip_size = 0; break; default: return 0; } oggs_size = 0x1b + (int)(data_size / 0xFF + 1); /* OggS page: base size + lacing values */ data->block_size = data_size + skip_size; data->page_size = oggs_size + data_size; if (data->page_size > sizeof(data->page_buffer)) { /* happens on bad reads/EOF too */ VGM_LOG("OPUS: buffer can't hold OggS at %x\n", (uint32_t)data->physical_offset); data->page_size = 0; break; } /* create fake OggS page (full page for checksums) */ read_streamfile(data->page_buffer+oggs_size, data->physical_offset + skip_size, data_size, streamfile); /* store page data */ data->samples_done += opus_get_packet_samples(data->page_buffer+oggs_size, data_size); make_oggs_page(data->page_buffer,sizeof(data->page_buffer), data_size, data->sequence, data->samples_done); data->sequence++; } /* move to next block */ if (offset >= data->logical_offset + data->page_size) { data->physical_offset += data->block_size; data->logical_offset += data->page_size; data->page_size = 0; continue; } /* read data */ { size_t bytes_consumed, to_read; bytes_consumed = offset - data->logical_offset; to_read = data->page_size - bytes_consumed; if (to_read > length) to_read = length; memcpy(dest, data->page_buffer + bytes_consumed, to_read); total_read += to_read; dest += to_read; offset += to_read; length -= to_read; if (to_read == 0) { break; /* error/EOF */ } } } return total_read; }
static size_t opus_io_size(STREAMFILE *streamfile, opus_io_data* data) { off_t physical_offset, max_physical_offset; size_t logical_size = 0; int packet = 0; if (data->logical_size) return data->logical_size; if (data->stream_offset + data->stream_size > get_streamfile_size(streamfile)) { VGM_LOG("OPUS: wrong streamsize %x + %x vs %x\n", (uint32_t)data->stream_offset, data->stream_size, get_streamfile_size(streamfile)); return 0; } physical_offset = data->stream_offset; max_physical_offset = data->stream_offset + data->stream_size; logical_size = data->head_size; /* get size of the logical stream */ while (physical_offset < max_physical_offset) { size_t data_size, skip_size, oggs_size; switch(data->type) { case OPUS_SWITCH: data_size = read_32bitBE(physical_offset, streamfile); skip_size = 0x08; break; case OPUS_UE4: data_size = (uint16_t)read_16bitLE(physical_offset, streamfile); skip_size = 0x02; break; case OPUS_EA: data_size = (uint16_t)read_16bitBE(physical_offset, streamfile); skip_size = 0x02; break; case OPUS_X: data_size = get_xopus_packet_size(packet, streamfile); skip_size = 0x00; break; default: return 0; } if (data_size == 0 ) { VGM_LOG("OPUS: data_size is 0 at %x\n", (uint32_t)physical_offset); return 0; /* bad rip? or could 'break' and truck along */ } oggs_size = 0x1b + (int)(data_size / 0xFF + 1); /* OggS page: base size + lacing values */ physical_offset += data_size + skip_size; logical_size += oggs_size + data_size; packet++; } /* logical size can be bigger though */ if (physical_offset > get_streamfile_size(streamfile)) { VGM_LOG("OPUS: wrong size\n"); return 0; } data->logical_size = logical_size; return data->logical_size; }
/* SCD - Square-Enix games (FF XIII, XIV) */ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset, tables_offset, meta_offset, extradata_offset, name_offset = 0; int32_t stream_size, extradata_size, loop_start, loop_end; int loop_flag = 0, channel_count, codec, sample_rate; int version, target_entry, aux_chunk_count; int total_subsongs, target_subsong = streamFile->stream_index; int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; /* check extension, case insensitive */ if ( !check_extensions(streamFile, "scd") ) goto fail; /** main header **/ if (read_32bitBE(0x00,streamFile) != 0x53454442 && /* "SEDB" */ read_32bitBE(0x04,streamFile) != 0x53534346) /* "SSCF" */ goto fail; if (read_8bit(0x0c,streamFile) == 0x01) { /* big endian flag */ //size_offset = 0x14; read_32bit = read_32bitBE; read_16bit = read_16bitBE; } else { //size_offset = 0x10; read_32bit = read_32bitLE; read_16bit = read_16bitLE; } /* SSCF version? (older SSCFs from Crisis Core/FFXI X360 seem to be V3/2) */ if (read_8bit(0x0d,streamFile) != 0x04) goto fail; /* v2: FFXIII demo (PS3), FFT0 test files (PC); v3: common; v4: Kingdom Hearts 2.8 (PS4) */ version = read_32bit(0x08,streamFile); if (version != 2 && version != 3 && version != 4) goto fail; tables_offset = read_16bit(0x0e,streamFile); /* usually 0x30 or 0x20 */ #if 0 /* never mind, FFXIII music_68tak.ps3.scd is 0x80 shorter */ /* check file size with header value */ if (read_32bit(size_offset,streamFile) != get_streamfile_size(streamFile)) goto fail; #endif /** offset tables **/ /* 0x00(2): table1/4 (unknown) entries */ /* 0x02(2): table2 (unknown) entries */ /* 0x04(2): table3 (headers) entries */ /* 0x06(2): unknown, varies even for clone files */ /* (implicit: table1 starts at 0x20) */ /* 0x08: table2 (unknown) start offset */ /* 0x0c: table3 (headers) start offset */ /* 0x10: table4 (unknown) start offset */ /* 0x14: always null? */ /* 0x18: table5? (unknown) start offset? */ /* 0x1c: unknown, often null */ /* each table entry is an uint32_t offset; after entries there is padding */ /* if a table isn't present entries is 0 and offset points to next table */ /* find meta_offset in table3 (headers) and total subsongs */ { int i; int headers_entries = read_16bit(tables_offset+0x04,streamFile); off_t headers_offset = read_32bit(tables_offset+0x0c,streamFile); if (target_subsong == 0) target_subsong = 1; total_subsongs = 0; meta_offset = 0; /* manually find subsongs as entries can be dummy (ex. sfx banks in FF XIV or FF Type-0) */ for (i = 0; i < headers_entries; i++) { off_t entry_offset = read_32bit(headers_offset + i*0x04,streamFile); if (read_32bit(entry_offset+0x0c,streamFile) == -1) continue; /* codec -1 when dummy */ total_subsongs++; if (!meta_offset && total_subsongs == target_subsong) { meta_offset = entry_offset; target_entry = i; } } if (meta_offset == 0) goto fail; /* SCD can contain 0 entries too */ } /** stream header **/ stream_size = read_32bit(meta_offset+0x00,streamFile); channel_count = read_32bit(meta_offset+0x04,streamFile); sample_rate = read_32bit(meta_offset+0x08,streamFile); codec = read_32bit(meta_offset+0x0c,streamFile); loop_start = read_32bit(meta_offset+0x10,streamFile); loop_end = read_32bit(meta_offset+0x14,streamFile); extradata_size = read_32bit(meta_offset+0x18,streamFile); aux_chunk_count = read_32bit(meta_offset+0x1c,streamFile); /* 0x01e(2): unknown, seen in some FF XIV sfx (MSADPCM) */ loop_flag = (loop_end > 0); extradata_offset = meta_offset + 0x20; start_offset = extradata_offset + extradata_size; /* only "MARK" chunk is known (some FF XIV PS3 have "STBL" but it's not counted) */ if (aux_chunk_count > 1 && aux_chunk_count < 0xFFFF) { /* some FF XIV Heavensward IMA sfx have 0x01000000 */ VGM_LOG("SCD: unknown aux chunk count %i\n", aux_chunk_count); goto fail; } /* skips aux chunks, sometimes needed (Lightning Returns X360, FF XIV PC) */ if (aux_chunk_count && read_32bitBE(extradata_offset, streamFile) == 0x4D41524B) { /* "MARK" */ extradata_offset += read_32bit(extradata_offset+0x04, streamFile); } /* find name if possible */ if (version == 4) { int info_entries = read_16bit(tables_offset+0x00,streamFile); int headers_entries = read_16bit(tables_offset+0x04,streamFile); off_t info_offset = tables_offset+0x20; /* not very exact as table1 and table3 entries may differ in V3, not sure about V4 */ if (info_entries == headers_entries) { off_t entry_offset = read_16bit(info_offset + 0x04*target_entry,streamFile); name_offset = entry_offset+0x30; } } #ifdef VGM_USE_VORBIS /* special case using init_vgmstream_ogg_vorbis */ if (codec == 0x06) { VGMSTREAM *ogg_vgmstream; uint8_t ogg_version, ogg_byte; ogg_vorbis_meta_info_t ovmi = {0}; ovmi.meta_type = meta_SQEX_SCD; ovmi.total_subsongs = total_subsongs; /* loop values are in bytes, let init_vgmstream_ogg_vorbis find loop comments instead */ ogg_version = read_8bit(extradata_offset + 0x00, streamFile); /* 0x01(1): 0x20 in v2/3, this ogg miniheader size? */ ogg_byte = read_8bit(extradata_offset + 0x02, streamFile); /* 0x03(1): ? in v3 */ if (ogg_version == 0) { /* 0x10? header, then custom Vorbis header before regular Ogg (FF XIV PC v1) */ ovmi.stream_size = stream_size; } else { /* 0x20 header, then seek table */ size_t seek_table_size = read_32bit(extradata_offset+0x10, streamFile); size_t vorb_header_size = read_32bit(extradata_offset+0x14, streamFile); /* 0x18(4): ? (can be 0) */ if ((extradata_offset-meta_offset) + seek_table_size + vorb_header_size != extradata_size) goto fail; ovmi.stream_size = vorb_header_size + stream_size; start_offset = extradata_offset + 0x20 + seek_table_size; /* extradata_size skips vorb_header */ if (ogg_version == 2) { /* header is XOR'ed using byte (FF XIV PC) */ ovmi.decryption_callback = scd_ogg_v2_decryption_callback; ovmi.scd_xor = ogg_byte; ovmi.scd_xor_length = vorb_header_size; } else if (ogg_version == 3) { /* file is XOR'ed using table (FF XIV Heavensward PC) */ ovmi.decryption_callback = scd_ogg_v3_decryption_callback; ovmi.scd_xor = stream_size & 0xFF; /* ogg_byte not used? */ ovmi.scd_xor_length = vorb_header_size + stream_size; } else { VGM_LOG("SCD: unknown ogg_version 0x%x\n", ogg_version); } } /* actual Ogg init */ ogg_vgmstream = init_vgmstream_ogg_vorbis_callbacks(streamFile, NULL, start_offset, &ovmi); if (ogg_vgmstream && name_offset) read_string(ogg_vgmstream->stream_name, PATH_LIMIT, name_offset, streamFile); return ogg_vgmstream; } #endif /* 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_SQEX_SCD; if (name_offset) read_string(vgmstream->stream_name, PATH_LIMIT, name_offset, streamFile); switch (codec) { case 0x01: /* PCM */ vgmstream->coding_type = coding_PCM16LE; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x02; vgmstream->num_samples = pcm_bytes_to_samples(stream_size, channel_count, 16); if (loop_flag) { vgmstream->loop_start_sample = pcm_bytes_to_samples(loop_start, channel_count, 16); vgmstream->loop_end_sample = pcm_bytes_to_samples(loop_end, channel_count, 16); } break; case 0x03: /* PS-ADPCM [Final Fantasy Type-0] */ vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x10; vgmstream->num_samples = ps_bytes_to_samples(stream_size, channel_count); if (loop_flag) { vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channel_count); vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channel_count); } break; case 0x06: /* OGG [Final Fantasy XIII-2 (PC), Final Fantasy XIV (PC)] */ goto fail; /* handled above */ #ifdef VGM_USE_MPEG case 0x07: { /* MPEG [Final Fantasy XIII (PS3)] */ mpeg_codec_data *mpeg_data = NULL; mpeg_custom_config cfg = {0}; cfg.interleave = 0x800; /* for multistream [Final Fantasy XIII-2 (PS3)], otherwise ignored */ cfg.data_size = stream_size; mpeg_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_SCD, &cfg); if (!mpeg_data) goto fail; vgmstream->codec_data = mpeg_data; vgmstream->layout_type = layout_none; /* some Drakengard 3, Kingdom Hearts HD have adjusted sample rate (47999, 44099), for looping? */ vgmstream->num_samples = mpeg_bytes_to_samples(stream_size, mpeg_data); vgmstream->loop_start_sample = mpeg_bytes_to_samples(loop_start, mpeg_data); vgmstream->loop_end_sample = mpeg_bytes_to_samples(loop_end, mpeg_data); /* somehow loops offsets aren't always frame-aligned, and the code below supposedly helped, * but there isn't much difference since MPEG loops are rough (1152-aligned). Seems it * would help more loop_start - ~1000, loop_end + ~1000 (ex. FFXIII-2 music_SunMizu.ps3.scd) */ //vgmstream->num_samples -= vgmstream->num_samples % 576; //vgmstream->loop_start_sample -= vgmstream->loop_start_sample % 576; //vgmstream->loop_end_sample -= vgmstream->loop_end_sample % 576; break; } #endif case 0x0C: /* MS ADPCM [Final Fantasy XIV (PC) sfx] */ vgmstream->coding_type = coding_MSADPCM; vgmstream->layout_type = layout_none; vgmstream->interleave_block_size = read_16bit(extradata_offset+0x0c,streamFile); /* in extradata_offset is a WAVEFORMATEX (including coefs and all) */ vgmstream->num_samples = msadpcm_bytes_to_samples(stream_size, vgmstream->interleave_block_size, vgmstream->channels); if (loop_flag) { vgmstream->loop_start_sample = msadpcm_bytes_to_samples(loop_start, vgmstream->interleave_block_size, vgmstream->channels); vgmstream->loop_end_sample = msadpcm_bytes_to_samples(loop_end, vgmstream->interleave_block_size, vgmstream->channels); } break; case 0x0A: /* DSP ADPCM [Dragon Quest X (Wii)] */ case 0x15: { /* DSP ADPCM [Dragon Quest X (Wii U)] (no apparent differences except higher sample rate) */ const off_t interleave_size = 0x800; const off_t stride_size = interleave_size * channel_count; int i; size_t total_size; layered_layout_data * data = NULL; /* interleaved DSPs including the header (so the first 0x800 is 0x60 header + 0x740 data) * so interleave layout can't used; we'll setup de-interleaving streamfiles as layers/channels instead */ //todo this could be simplified using a block layout or adding interleave_first_block vgmstream->coding_type = coding_NGC_DSP; vgmstream->layout_type = layout_layered; /* read from the first DSP header and verify other channel headers */ { total_size = (read_32bitBE(start_offset+0x04,streamFile)+1)/2; /* rounded nibbles / 2 */ vgmstream->num_samples = read_32bitBE(start_offset+0x00,streamFile); if (loop_flag) { vgmstream->loop_start_sample = loop_start; vgmstream->loop_end_sample = loop_end+1; } for (i = 1; i < channel_count; i++) { if ((read_32bitBE(start_offset+4,streamFile)+1)/2 != total_size || read_32bitBE(start_offset+interleave_size*i+0x00,streamFile) != vgmstream->num_samples) { goto fail; } } } /* init layout */ data = init_layout_layered(channel_count); if (!data) goto fail; vgmstream->layout_data = data; /* open each layer subfile */ for (i = 0; i < channel_count; i++) { STREAMFILE* temp_streamFile = setup_scd_dsp_streamfile(streamFile, start_offset+interleave_size*i, interleave_size, stride_size, total_size); if (!temp_streamFile) goto fail; data->layers[i] = init_vgmstream_ngc_dsp_std(temp_streamFile); close_streamfile(temp_streamFile); if (!data->layers[i]) goto fail; } /* setup layered VGMSTREAMs */ if (!setup_layout_layered(data)) goto fail; break; } #ifdef VGM_USE_FFMPEG case 0x0B: { /* XMA2 [Final Fantasy (X360), Lightning Returns (X360) sfx, Kingdom Hearts 2.8 (X1)] */ ffmpeg_codec_data *ffmpeg_data = NULL; uint8_t buf[200]; int32_t bytes; /* extradata_offset+0x00: fmt0x166 header (BE), extradata_offset+0x34: seek table */ bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,200, extradata_offset,0x34, stream_size, streamFile, 1); ffmpeg_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,stream_size); if (!ffmpeg_data) goto fail; vgmstream->codec_data = ffmpeg_data; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; vgmstream->num_samples = ffmpeg_data->totalSamples; vgmstream->loop_start_sample = loop_start; vgmstream->loop_end_sample = loop_end; xma_fix_raw_samples(vgmstream, streamFile, start_offset,stream_size, 0, 0,0); /* samples are ok, loops? */ break; } case 0x0E: { /* ATRAC3/ATRAC3plus [Lord of Arcana (PSP), Final Fantasy Type-0] */ ffmpeg_codec_data *ffmpeg_data = NULL; /* full RIFF header at start_offset/extradata_offset (same) */ 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; vgmstream->num_samples = ffmpeg_data->totalSamples; /* fact samples */ vgmstream->loop_start_sample = loop_start; vgmstream->loop_end_sample = loop_end; if (ffmpeg_data->skipSamples <= 0) /* in case FFmpeg didn't get them */ ffmpeg_set_skip_samples(ffmpeg_data, riff_get_fact_skip_samples(streamFile, start_offset)); /* SCD loop/sample values are relative (without skip samples) vs RIFF (with skip samples), no need to adjust */ break; } #endif #ifdef VGM_USE_ATRAC9 case 0x16: { /* ATRAC9 [Kingdom Hearts 2.8 (PS4)] */ atrac9_config cfg = {0}; /* post header has various typical ATRAC9 values */ cfg.channels = vgmstream->channels; cfg.config_data = read_32bit(extradata_offset+0x0c,streamFile); cfg.encoder_delay = read_32bit(extradata_offset+0x18,streamFile); vgmstream->codec_data = init_atrac9(&cfg); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_ATRAC9; vgmstream->layout_type = layout_none; vgmstream->num_samples = read_32bit(extradata_offset+0x10,streamFile); /* loop values above are also weird and ignored */ vgmstream->loop_start_sample = read_32bit(extradata_offset+0x20, streamFile) - (loop_flag ? cfg.encoder_delay : 0); //loop_start vgmstream->loop_end_sample = read_32bit(extradata_offset+0x24, streamFile) - (loop_flag ? cfg.encoder_delay : 0); //loop_end break; } #endif case -1: /* used for dummy entries */ default: VGM_LOG("SCD: unknown codec 0x%x\n", codec); goto fail; } if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }
/* FLX - from Ultima IX (.FLX is actually an archive format with sometimes sound data, let's support both anyway) */ VGMSTREAM * init_vgmstream_flx(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset, stream_offset = 0; size_t data_size; int loop_flag, channel_count, codec; int total_subsongs = 0, target_subsong = streamFile->stream_index; size_t stream_size = 0; /* check extensions (.flx: name of archive, files inside don't have extensions) */ if (!check_extensions(streamFile,"flx")) goto fail; /* all spaces up to 0x50 = archive FLX */ if (read_32bitBE(0x00,streamFile) == 0x20202020 && read_32bitBE(0x40,streamFile) == 0x20202020) { int i; int entries = read_32bitLE(0x50,streamFile); off_t offset = 0x80; if (read_32bitLE(0x54,streamFile) != 0x02 || read_32bitLE(0x58,streamFile) != get_streamfile_size(streamFile)) goto fail; if (target_subsong == 0) target_subsong = 1; for (i = 0; i < entries; i++) { off_t entry_offset = read_32bitLE(offset + 0x00, streamFile); size_t entry_size = read_32bitLE(offset + 0x04, streamFile); offset += 0x08; if (entry_offset != 0x00) total_subsongs++; /* many entries are empty */ if (total_subsongs == target_subsong && stream_offset == 0) { stream_offset = entry_offset; /* found but let's keep adding total_streams */ stream_size = entry_size; } } if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; if (stream_offset == 0x00) goto fail; } else { stream_offset = 0x00; stream_size = get_streamfile_size(streamFile); } if (read_32bitLE(stream_offset + 0x30,streamFile) != 0x10) goto fail; data_size = read_32bitLE(stream_offset + 0x28,streamFile); channel_count = read_32bitLE(stream_offset + 0x34,streamFile); codec = read_32bitLE(stream_offset + 0x38,streamFile); loop_flag = (channel_count > 1); /* full seamless repeats in music */ start_offset = stream_offset + 0x3c; /* 0x00: id */ /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = read_32bitLE(stream_offset + 0x2c,streamFile); vgmstream->num_streams = total_subsongs; vgmstream->stream_size = stream_size; vgmstream->meta_type = meta_PC_FLX; switch(codec) { case 0x00: /* PCM (sfx) */ vgmstream->coding_type = coding_PCM16LE; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x02; vgmstream->num_samples = pcm_bytes_to_samples(data_size, channel_count, 16); break; case 0x01: /* EA-XA (music, sfx) */ vgmstream->coding_type = channel_count > 1 ? coding_EA_XA : coding_EA_XA_int; vgmstream->layout_type = layout_none; vgmstream->num_samples = read_32bitLE(stream_offset + 0x28,streamFile) / 0x0f*channel_count * 28; /* ea_xa_bytes_to_samples */ vgmstream->loop_start_sample = 0; vgmstream->loop_end_sample = vgmstream->num_samples; break; case 0x02: /* EA-MT (voices) */ vgmstream->coding_type = coding_EA_MT; vgmstream->codec_data = init_ea_mt(vgmstream->channels, 0); if (!vgmstream->codec_data) goto fail; vgmstream->num_samples = read_32bitLE(start_offset,streamFile); start_offset += 0x04; break; default: VGM_LOG("FLX: unknown codec 0x%x\n", codec); goto fail; } read_string(vgmstream->stream_name,0x20+1, stream_offset + 0x04,streamFile); /* open the file for reading */ if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; fail: close_vgmstream(vgmstream); return NULL; }