コード例 #1
0
ファイル: mogg.c プロジェクト: benladen/vgmstream
/* MOGG - Harmonix Music Systems (Guitar Hero)[Unencrypted Type] */
VGMSTREAM * init_vgmstream_mogg(STREAMFILE *streamFile) {
#ifdef VGM_USE_VORBIS
	char filename[PATH_LIMIT];
	off_t start_offset;

	/* check extension, case insensitive */
	streamFile->get_name(streamFile, filename, sizeof(filename));
	if (strcasecmp("mogg", filename_extension(filename))) goto fail;

	{
		vgm_vorbis_info_t inf;
		VGMSTREAM * result = NULL;

		memset(&inf, 0, sizeof(inf));
		inf.layout_type = layout_ogg_vorbis;
		inf.meta_type = meta_MOGG;

		start_offset = read_32bitLE(0x04, streamFile);
		result = init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);

		if (result != NULL) {
			return result;
		}
	}

fail:
	/* clean up anything we may have opened */
#endif
	return NULL;
}
コード例 #2
0
ファイル: rsd.c プロジェクト: xbmcin/XBMCinTC
/* RSD6OGG */
VGMSTREAM * init_vgmstream_rsd6oogv(STREAMFILE *streamFile) {
#ifdef VGM_USE_VORBIS
    char filename[260];
    off_t start_offset;

    /* check extension, case insensitive */
    streamFile->get_name(streamFile,filename,sizeof(filename));
    if (strcasecmp("rsd",filename_extension(filename))) goto fail;

    /* check header */
    if (read_32bitBE(0x0,streamFile) != 0x52534436) /* RSD6 */
		goto fail;
	if (read_32bitBE(0x4,streamFile) != 0x4F4F4756)	/* OOGV */
        goto fail;

    {
        vgm_vorbis_info_t inf;
        VGMSTREAM * result = NULL;

        memset(&inf, 0, sizeof(inf));
        inf.layout_type = layout_ogg_vorbis;
        inf.meta_type = meta_RSD6OOGV;

        start_offset = 0x800;
		result = init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);

        if (result != NULL) {
            return result;
        }
	   }

fail:
    /* clean up anything we may have opened */
#endif
    return NULL;
}
コード例 #3
0
ファイル: ogg_vorbis_file.c プロジェクト: 9a3eedi/Droidsound
VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
    char filename[260];

    ov_callbacks callbacks;

    off_t other_header_bytes = 0;
    int um3_ogg = 0;
    int kovs_ogg = 0;
    int psych_ogg = 0;

    vgm_vorbis_info_t inf;
    memset(&inf, 0, sizeof(inf));

    /* check extension, case insensitive */
    streamFile->get_name(streamFile,filename,sizeof(filename));
    
    /* It is only interesting to use oggs with vgmstream if they are looped.
       To prevent such files from being played by other plugins and such they
       may be renamed to .logg. This meta reader should still support .ogg,
       though. */
    if (strcasecmp("logg",filename_extension(filename)) &&
            strcasecmp("ogg",filename_extension(filename))) {
        if (!strcasecmp("um3",filename_extension(filename))) {
            um3_ogg = 1;
        } else if (!strcasecmp("kovs",filename_extension(filename))) {
            kovs_ogg = 1;
        } else {
            goto fail;
        }
    }

    /* not all um3-ogg are crypted */
    if (um3_ogg && read_32bitBE(0x0,streamFile)==0x4f676753) {
        um3_ogg = 0;
    }

    /* use KOVS header */
    if (kovs_ogg) {
        if (read_32bitBE(0x0,streamFile)!=0x4b4f5653) { /* "KOVS" */
            goto fail;
        }
        if (read_32bitLE(0x8,streamFile)!=0) {
            inf.loop_start = read_32bitLE(0x8,streamFile);
            inf.loop_flag = 1;
        }

        other_header_bytes = 0x20;
    }

    /* detect Psychic Software obfuscation (as seen in "Darkwind") */
    if (read_32bitBE(0x0,streamFile)==0x2c444430) {
        psych_ogg = 1;
    }

    if (um3_ogg) {
        callbacks.read_func = read_func_um3;
    } else if (kovs_ogg) {
        callbacks.read_func = read_func_kovs;
    } else if (psych_ogg) {
        callbacks.read_func = read_func_psych;
    } else {
        callbacks.read_func = read_func;
    }
    callbacks.seek_func = seek_func;
    callbacks.close_func = close_func;
    callbacks.tell_func = tell_func;

    if (um3_ogg) {
        inf.meta_type = meta_um3_ogg;
    } else if (kovs_ogg) {
        inf.meta_type = meta_KOVS_ogg;
    } else if (psych_ogg) {
        inf.meta_type = meta_psych_ogg;
    } else {
        inf.meta_type = meta_ogg_vorbis;
    }

    inf.layout_type = layout_ogg_vorbis;

    return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, &callbacks, other_header_bytes, &inf);

fail:
    return NULL;
}
コード例 #4
0
VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
    VGMSTREAM * vgmstream = NULL;
    char filename[PATH_LIMIT];
    off_t start_offset, meta_offset_offset, meta_offset, post_meta_offset;
    int32_t loop_start, loop_end;

    int loop_flag = 0;
	int channel_count;
    int codec_id;
    int aux_chunk_count;

    int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
    int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;

    /* check extension, case insensitive */
    streamFile->get_name(streamFile,filename,sizeof(filename));
    if (strcasecmp("scd",filename_extension(filename))) goto fail;

    /* SEDB */
    if (read_32bitBE(0,streamFile) != 0x53454442) goto fail;
    /* SSCF */
    if (read_32bitBE(4,streamFile) != 0x53534346) goto fail;
    if (read_32bitBE(8,streamFile) == 2 ||
        read_32bitBE(8,streamFile) == 3) {
        /* version 2 BE, as seen in FFXIII demo for PS3 */
        /* version 3 BE, as seen in FFXIII for PS3 */
        read_32bit = read_32bitBE;
        read_16bit = read_16bitBE;
        //size_offset = 0x14;
        meta_offset_offset = 0x40 + read_16bit(0xe,streamFile);
    } else if (read_32bitLE(8,streamFile) == 3 ||
               read_32bitLE(8,streamFile) == 2) {
        /* version 2/3 LE, as seen in FFXIV for ?? */
        read_32bit = read_32bitLE;
        read_16bit = read_16bitLE;
        //size_offset = 0x10;
        meta_offset_offset = 0x40 + read_16bit(0xe,streamFile);
    } else goto fail;

    /* never mind, FFXIII music_68tak.ps3.scd is 0x80 shorter */
#if 0
    /* check file size with header value */
    if (read_32bit(size_offset,streamFile) != get_streamfile_size(streamFile))
        goto fail;
#endif

    meta_offset = read_32bit(meta_offset_offset,streamFile);

    /* check that chunk size equals stream size (?) */
    loop_start = read_32bit(meta_offset+0x10,streamFile);
    loop_end = read_32bit(meta_offset+0x14,streamFile);
    loop_flag = (loop_end > 0);

    channel_count = read_32bit(meta_offset+4,streamFile);
    codec_id = read_32bit(meta_offset+0xc,streamFile);

    post_meta_offset = meta_offset + 0x20;

    /* data at meta_offset is only 0x20 bytes, but there may be auxiliary chunks
       before anything else */

    aux_chunk_count = read_32bit(meta_offset+0x1c,streamFile);
    for (; aux_chunk_count > 0; aux_chunk_count --)
    {
        /* skip aux chunks */
        /*printf("skipping %08x\n", read_32bitBE(post_meta_offset, streamFile));*/
        post_meta_offset += read_32bit(post_meta_offset+4,streamFile);
    }

    start_offset = post_meta_offset + read_32bit(meta_offset+0x18,streamFile);

#ifdef VGM_USE_VORBIS
    if (codec_id == 0x6)
    {
        vgm_vorbis_info_t inf;
        uint32_t seek_table_size = read_32bit(post_meta_offset+0x10, streamFile);
        uint32_t vorb_header_size = read_32bit(post_meta_offset+0x14, streamFile);
        VGMSTREAM * result = NULL;

        memset(&inf, 0, sizeof(inf));
        inf.loop_start = loop_start;
        inf.loop_end = loop_end;
        inf.loop_flag = loop_flag;
        inf.loop_end_found = loop_flag;
        inf.loop_length_found = 0;
        inf.layout_type = layout_ogg_vorbis;
        inf.meta_type = meta_SQEX_SCD;

        result = init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);

        if (result != NULL) {
            return result;
        }

        // try skipping seek table
        {
            if ((post_meta_offset-meta_offset) + seek_table_size + vorb_header_size != read_32bit(meta_offset+0x18, streamFile)) {
                return NULL;
            }

            start_offset = post_meta_offset + 0x20 + seek_table_size;
            result = init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);
            if (result != NULL) {
                return result;
            }
        }

        // failed with Ogg, try deobfuscating header
        {
            // skip chunks before xor_byte
            unsigned char xor_byte;

            xor_byte = read_8bit(post_meta_offset+2, streamFile);

            if (xor_byte == 0) {
                return NULL;
            }

            inf.scd_xor = xor_byte;
            inf.scd_xor_len = vorb_header_size;

            result = init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);
            return result;
        }
    }
#endif
    
	/* build the VGMSTREAM */
    vgmstream = allocate_vgmstream(channel_count,loop_flag);
    if (!vgmstream) goto fail;

	/* fill in the vital statistics */
	vgmstream->channels = channel_count;
    vgmstream->sample_rate = read_32bit(meta_offset+8,streamFile);

    switch (codec_id) {
        case 0x1:
            /* PCM */
            vgmstream->coding_type = coding_PCM16LE_int;
            vgmstream->layout_type = layout_none;
            vgmstream->num_samples = read_32bit(meta_offset+0,streamFile) / 2 / channel_count;

            if (loop_flag) {
                vgmstream->loop_start_sample = loop_start / 2 / channel_count;
                vgmstream->loop_end_sample = loop_end / 2 / channel_count;
            }
            break;
#ifdef VGM_USE_MPEG
        case 0x7:
            /* MPEG */
            {
                mpeg_codec_data *mpeg_data = NULL;
                struct mpg123_frameinfo mi;
                coding_t ct;

                if (vgmstream->sample_rate == 47999)
                    vgmstream->sample_rate = 48000;
                if (vgmstream->sample_rate == 44099)
                    vgmstream->sample_rate = 44100;

                mpeg_data = init_mpeg_codec_data(streamFile, start_offset, vgmstream->sample_rate, vgmstream->channels, &ct, NULL, NULL);
                if (!mpeg_data) goto fail;
                vgmstream->codec_data = mpeg_data;

                if (MPG123_OK != mpg123_info(mpeg_data->m, &mi)) goto fail;

                vgmstream->coding_type = ct;
                vgmstream->layout_type = layout_mpeg;
                if (mi.vbr != MPG123_CBR) goto fail;
                vgmstream->num_samples = mpeg_bytes_to_samples(read_32bit(meta_offset+0,streamFile), &mi);
                vgmstream->num_samples -= vgmstream->num_samples%576;
                if (loop_flag) {
                    vgmstream->loop_start_sample = mpeg_bytes_to_samples(loop_start, &mi);
                    vgmstream->loop_start_sample -= vgmstream->loop_start_sample%576;
                    vgmstream->loop_end_sample = mpeg_bytes_to_samples(loop_end, &mi);
                    vgmstream->loop_end_sample -= vgmstream->loop_end_sample%576;
                }
                vgmstream->interleave_block_size = 0;
            }
            break;
#endif
        case 0xC:
            /* MS ADPCM */
            vgmstream->coding_type = coding_MSADPCM;
            vgmstream->layout_type = layout_none;
            vgmstream->interleave_block_size = read_16bit(post_meta_offset+0xc,streamFile);
            vgmstream->num_samples = msadpcm_bytes_to_samples(read_32bit(meta_offset+0,streamFile), 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 0xA:
            /* GC/Wii DSP ADPCM */
            {
                STREAMFILE * file;
                int i;
                const off_t interleave_size = 0x800;
                const off_t stride_size = interleave_size * channel_count;

                size_t total_size;

                scd_int_codec_data * data = NULL;

                vgmstream->coding_type = coding_NGC_DSP;
                vgmstream->layout_type = layout_scd_int;

                /* a normal DSP header... */
                vgmstream->num_samples = read_32bitBE(start_offset+0,streamFile);
                total_size = (read_32bitBE(start_offset+4,streamFile)+1)/2;

                if (loop_flag) {
                    vgmstream->loop_start_sample = loop_start;
                    vgmstream->loop_end_sample = loop_end+1;
                }

                /* verify other channel headers */
                for (i = 1; i < channel_count; i++) {
                    if (read_32bitBE(start_offset+interleave_size*i+0,streamFile) != vgmstream->num_samples ||
                        (read_32bitBE(start_offset+4,streamFile)+1)/2 != total_size) {
                        goto fail;
                    }

                }

                /* the primary streamfile we'll be using */
                file = streamFile->open(streamFile,filename,stride_size);
                if (!file)
                    goto fail;

                vgmstream->ch[0].streamfile = file;

                data = malloc(sizeof(scd_int_codec_data));
                data->substream_count = channel_count;
                data->substreams = calloc(channel_count, sizeof(VGMSTREAM *));
                data->intfiles = calloc(channel_count, sizeof(STREAMFILE *));

                vgmstream->codec_data = data;

                for (i=0;i<channel_count;i++) {
                    STREAMFILE * intfile =
                        open_scdint_with_STREAMFILE(file, "ARBITRARY.DSP", start_offset+interleave_size*i, interleave_size, stride_size, total_size);

                    data->substreams[i] = init_vgmstream_ngc_dsp_std(intfile);
                    data->intfiles[i] = intfile;
                    if (!data->substreams[i])
                        goto fail;

                    /* TODO: only handles mono substreams, though that's all we have with DSP */
                    /* save start things so we can restart for seeking/looping */
                    /* copy the channels */
                    memcpy(data->substreams[i]->start_ch,data->substreams[i]->ch,sizeof(VGMSTREAMCHANNEL)*1);
                    /* copy the whole VGMSTREAM */
                    memcpy(data->substreams[i]->start_vgmstream,data->substreams[i],sizeof(VGMSTREAM));

                }

            }
            break;
#ifdef VGM_USE_FFMPEG
        case 0xB:
            /* XMA1/XMA2 */
            {
                uint16_t codec_id = read_16bit(post_meta_offset, streamFile);
                if (codec_id == 0x165 || codec_id == 0x166)
                {
                    ffmpeg_codec_data *ffmpeg_data = init_ffmpeg_faux_riff(streamFile, post_meta_offset, start_offset, streamFile->get_size(streamFile) - start_offset, read_32bit == read_32bitBE);
                    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;

                    if (loop_flag) {
                        vgmstream->loop_start_sample = loop_start;
                        vgmstream->loop_end_sample = loop_end;
                    }
                }
                else goto fail;
            }
            break;
#endif
        default:
            goto fail;
    }

    vgmstream->meta_type = meta_SQEX_SCD;

    /* open the file for reading */
    if (vgmstream->layout_type != layout_scd_int
#ifdef VGM_USE_FFMPEG
        && vgmstream->coding_type != coding_FFmpeg
#endif
        )
    {
        int i;
        STREAMFILE * file;
        file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
        if (!file) goto fail;
        for (i=0;i<channel_count;i++) {
            vgmstream->ch[i].streamfile = file;

            vgmstream->ch[i].channel_start_offset=
                vgmstream->ch[i].offset=start_offset;

        }
    }

    return vgmstream;

    /* clean up anything we may have opened */
fail:
    if (vgmstream) close_vgmstream(vgmstream);
    return NULL;
}
コード例 #5
0
ファイル: sqex_scd.c プロジェクト: kode54/vgmstream
/* 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;
}