From 15d794bbe2746e1ec88642aadc6aaca7a13df357 Mon Sep 17 00:00:00 2001 From: bnnm Date: Fri, 18 Oct 2019 19:34:15 +0200 Subject: [PATCH] Allow Ogg decoder to be used like other codecs --- src/coding/coding.h | 9 +- src/coding/ogg_vorbis_decoder.c | 243 +++++++++++++++++++++-- src/meta/meta.h | 2 +- src/meta/ogg_vorbis.c | 339 ++++++++++---------------------- src/meta/sqex_scd.c | 31 +-- src/vgmstream.c | 3 +- src/vgmstream.h | 13 +- 7 files changed, 363 insertions(+), 277 deletions(-) diff --git a/src/coding/coding.h b/src/coding/coding.h index c56a28c2..507c9c9e 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -212,11 +212,18 @@ int test_hca_key(hca_codec_data * data, unsigned long long keycode); #ifdef VGM_USE_VORBIS /* ogg_vorbis_decoder */ -void decode_ogg_vorbis(ogg_vorbis_codec_data * data, sample_t * outbuf, int32_t samples_to_do, int channels); +ogg_vorbis_codec_data* init_ogg_vorbis(STREAMFILE *sf, off_t start, off_t size, ogg_vorbis_io *io); +void decode_ogg_vorbis(ogg_vorbis_codec_data *data, sample_t *outbuf, int32_t samples_to_do, int channels); void reset_ogg_vorbis(VGMSTREAM *vgmstream); void seek_ogg_vorbis(VGMSTREAM *vgmstream, int32_t num_sample); void free_ogg_vorbis(ogg_vorbis_codec_data *data); +int ogg_vorbis_get_comment(ogg_vorbis_codec_data *data, const char **comment); +void ogg_vorbis_get_info(ogg_vorbis_codec_data *data, int *p_channels, int *p_sample_rate); +void ogg_vorbis_get_samples(ogg_vorbis_codec_data *data, int *p_samples); +void ogg_vorbis_set_disable_reordering(ogg_vorbis_codec_data *data, int set); +STREAMFILE* ogg_vorbis_get_streamfile(ogg_vorbis_codec_data *data); + /* vorbis_custom_decoder */ vorbis_custom_codec_data *init_vorbis_custom(STREAMFILE *streamfile, off_t start_offset, vorbis_custom_t type, vorbis_custom_config * config); void decode_vorbis_custom(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels); diff --git a/src/coding/ogg_vorbis_decoder.c b/src/coding/ogg_vorbis_decoder.c index 198d14aa..83566456 100644 --- a/src/coding/ogg_vorbis_decoder.c +++ b/src/coding/ogg_vorbis_decoder.c @@ -5,21 +5,183 @@ #ifdef VGM_USE_VORBIS #include +#define OGG_DEFAULT_BITSTREAM 0 -static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples_to_do, float ** pcm, int disable_ordering); +/* opaque struct */ +struct ogg_vorbis_codec_data { + OggVorbis_File ogg_vorbis_file; + int ovf_init; + int bitstream; + int disable_reordering; /* Xiph Ogg must reorder channels on output, but some pre-ordered games don't need it */ + + ogg_vorbis_io io; + vorbis_comment *comment; + int comment_number; + vorbis_info *info; +}; -void decode_ogg_vorbis(ogg_vorbis_codec_data * data, sample_t * outbuf, int32_t samples_to_do, int channels) { +static void pcm_convert_float_to_16(int channels, sample_t *outbuf, int samples_to_do, float **pcm, int disable_ordering); + +static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void *datasource); +static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence); +static long ov_tell_func(void *datasource); +static int ov_close_func(void *datasource); + + +ogg_vorbis_codec_data* init_ogg_vorbis(STREAMFILE *sf, off_t start, off_t size, ogg_vorbis_io *io) { + ogg_vorbis_codec_data *data = NULL; + ov_callbacks callbacks = {0}; + + //todo clean up + + callbacks.read_func = ov_read_func; + callbacks.seek_func = ov_seek_func; + callbacks.close_func = ov_close_func; + callbacks.tell_func = ov_tell_func; + + /* test if this is a proper Ogg Vorbis file, with the current (from init_x) STREAMFILE + * (quick test without having to malloc first, though if one invoked this it'll probably success) */ + { + OggVorbis_File temp_ovf = {0}; + ogg_vorbis_io temp_io = {0}; + + temp_io.streamfile = sf; + + temp_io.start = start; + temp_io.offset = 0; + temp_io.size = size; + + if (io != NULL) { + temp_io.decryption_callback = io->decryption_callback; + temp_io.scd_xor = io->scd_xor; + temp_io.scd_xor_length = io->scd_xor_length; + temp_io.xor_value = io->xor_value; + } + + /* open the ogg vorbis file for testing */ + if (ov_test_callbacks(&temp_io, &temp_ovf, NULL, 0, callbacks)) + goto fail; + + /* we have to close this as it has the init_vgmstream meta-reading STREAMFILE */ + ov_clear(&temp_ovf); + } + + /* proceed to init codec_data and reopen a STREAMFILE for this codec */ + { + data = calloc(1,sizeof(ogg_vorbis_codec_data)); + if (!data) goto fail; + + data->io.streamfile = reopen_streamfile(sf, 0); + if (!data->io.streamfile) goto fail; + + data->io.start = start; + data->io.offset = 0; + data->io.size = size; + + if (io != NULL) { + data->io.decryption_callback = io->decryption_callback; + data->io.scd_xor = io->scd_xor; + data->io.scd_xor_length = io->scd_xor_length; + data->io.xor_value = io->xor_value; + } + + /* open the ogg vorbis file for real */ + if (ov_open_callbacks(&data->io, &data->ogg_vorbis_file, NULL, 0, callbacks)) + goto fail; + data->ovf_init = 1; + } + + //todo could set bitstreams as subsongs? + /* get info from bitstream */ + data->bitstream = OGG_DEFAULT_BITSTREAM; + + data->comment = ov_comment(&data->ogg_vorbis_file, data->bitstream); + data->info = ov_info(&data->ogg_vorbis_file, data->bitstream); + + return data; +fail: + free_ogg_vorbis(data); + return NULL; +} + +static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void *datasource) { + ogg_vorbis_io *io = datasource; + size_t bytes_read, items_read; + + off_t real_offset = io->start + io->offset; + size_t max_bytes = size * nmemb; + + /* clamp for virtual filesize */ + if (max_bytes > io->size - io->offset) + max_bytes = io->size - io->offset; + + bytes_read = read_streamfile(ptr, real_offset, max_bytes, io->streamfile); + items_read = bytes_read / size; + + /* may be encrypted */ + if (io->decryption_callback) { + io->decryption_callback(ptr, size, items_read, io); + } + + io->offset += items_read * size; + + return items_read; +} + +static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) { + ogg_vorbis_io *io = datasource; + ogg_int64_t base_offset, new_offset; + + switch (whence) { + case SEEK_SET: + base_offset = 0; + break; + case SEEK_CUR: + base_offset = io->offset; + break; + case SEEK_END: + base_offset = io->size; + break; + default: + return -1; + break; + } + + + new_offset = base_offset + offset; + if (new_offset < 0 || new_offset > io->size) { + return -1; /* *must* return -1 if stream is unseekable */ + } else { + io->offset = new_offset; + return 0; + } +} + +static long ov_tell_func(void *datasource) { + ogg_vorbis_io *io = datasource; + return io->offset; +} + +static int ov_close_func(void *datasource) { + /* needed as setting ov_close_func in ov_callbacks to NULL doesn't seem to work + * (closing the streamfile is done in free_ogg_vorbis) */ + return 0; +} + +/* ********************************************** */ + +void decode_ogg_vorbis(ogg_vorbis_codec_data *data, sample_t *outbuf, int32_t samples_to_do, int channels) { int samples_done = 0; long rc; float **pcm_channels; /* pointer to Xiph's double array buffer */ while (samples_done < samples_to_do) { rc = ov_read_float( - &data->ogg_vorbis_file, /* context */ - &pcm_channels, /* buffer pointer */ - (samples_to_do - samples_done), /* samples to produce */ - &data->bitstream); /* bitstream*/ + &data->ogg_vorbis_file, /* context */ + &pcm_channels, /* buffer pointer */ + (samples_to_do - samples_done), /* samples to produce */ + &data->bitstream); /* bitstream */ if (rc <= 0) goto fail; /* rc is samples done */ pcm_convert_float_to_16(channels, outbuf, rc, pcm_channels, data->disable_reordering); @@ -32,13 +194,13 @@ void decode_ogg_vorbis(ogg_vorbis_codec_data * data, sample_t * outbuf, int32_t /* we use ov_read_float as to reuse the xiph's buffer for easier remapping, * but seems ov_read is slightly faster due to optimized (asm) float-to-int. */ rc = ov_read( - &data->ogg_vorbis_file, /* context */ - (char *)(outbuf), /* buffer */ + &data->ogg_vorbis_file, /* context */ + (char *)(outbuf), /* buffer */ (samples_to_do - samples_done) * sizeof(sample_t) * channels, /* length in bytes */ - 0, /* pcm endianness */ - sizeof(sample), /* pcm size */ - 1, /* pcm signedness */ - &data->bitstream); /* bitstream */ + 0, /* pcm endianness */ + sizeof(sample), /* pcm size */ + 1, /* pcm signedness */ + &data->bitstream); /* bitstream */ if (rc <= 0) goto fail; /* rc is bytes done (for all channels) */ swap_samples_le(outbuf, rc / sizeof(sample_t)); /* endianness is a bit weird with ov_read though */ @@ -56,7 +218,7 @@ fail: /* vorbis encodes channels in non-standard order, so we remap during conversion to fix this oddity. * (feels a bit weird as one would think you could leave as-is and set the player's output - * order, but that isn't possible and remapping is what FFmpeg and every other plugin do). */ + * order, but that isn't possible and remapping is what FFmpeg and every other plugin does). */ static const int xiph_channel_map[8][8] = { { 0 }, /* 1ch: FC > same */ { 0, 1 }, /* 2ch: FL FR > same */ @@ -115,10 +277,61 @@ void seek_ogg_vorbis(VGMSTREAM *vgmstream, int32_t num_sample) { void free_ogg_vorbis(ogg_vorbis_codec_data *data) { if (!data) return; - ov_clear(&data->ogg_vorbis_file); + if (data->ovf_init) + ov_clear(&data->ogg_vorbis_file); - close_streamfile(data->ov_streamfile.streamfile); + close_streamfile(data->io.streamfile); free(data); } +/* ********************************************** */ + +int ogg_vorbis_get_comment(ogg_vorbis_codec_data *data, const char **comment) { + if (!data) return 0; + + /* dumb reset */ + if (comment == NULL) { + data->comment_number = 0; + return 1; + } + + if (data->comment_number >= data->comment->comments) + return 0; + + *comment = data->comment->user_comments[data->comment_number]; + data->comment_number++; + return 1; +} + +void ogg_vorbis_get_info(ogg_vorbis_codec_data *data, int *p_channels, int *p_sample_rate) { + if (!data) { + if (p_channels) *p_channels = 0; + if (p_sample_rate) *p_sample_rate = 0; + return; + } + + if (p_channels) *p_channels = data->info->channels; + if (p_sample_rate) *p_sample_rate = (int)data->info->rate; +} + +void ogg_vorbis_get_samples(ogg_vorbis_codec_data *data, int *p_samples) { + if (!data) { + if (p_samples) *p_samples = 0; + return; + } + + if (p_samples) *p_samples = ov_pcm_total(&data->ogg_vorbis_file,-1); +} + +void ogg_vorbis_set_disable_reordering(ogg_vorbis_codec_data *data, int set) { + if (!data) return; + + data->disable_reordering = set; +} + +STREAMFILE* ogg_vorbis_get_streamfile(ogg_vorbis_codec_data *data) { + if (!data) return NULL; + return data->io.streamfile; +} + #endif diff --git a/src/meta/meta.h b/src/meta/meta.h index edf0eefc..87b24c54 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -128,7 +128,7 @@ typedef struct { } ogg_vorbis_meta_info_t; -VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callbacks *callbacks, off_t other_header_bytes, const ogg_vorbis_meta_info_t *ovmi); +VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callbacks *callbacks, off_t start, const ogg_vorbis_meta_info_t *ovmi); #endif VGMSTREAM * init_vgmstream_hca(STREAMFILE *streamFile); diff --git a/src/meta/ogg_vorbis.c b/src/meta/ogg_vorbis.c index 7f90b179..d1ed10f4 100644 --- a/src/meta/ogg_vorbis.c +++ b/src/meta/ogg_vorbis.c @@ -1,123 +1,60 @@ -#include "../vgmstream.h" - #ifdef VGM_USE_VORBIS #include #include -#include #include "meta.h" +#include "../coding/coding.h" #include "ogg_vorbis_streamfile.h" -#define OGG_DEFAULT_BITSTREAM 0 - -static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void * datasource) { - ogg_vorbis_streamfile * const ov_streamfile = datasource; - size_t bytes_read, items_read; - - off_t real_offset = ov_streamfile->start + ov_streamfile->offset; - size_t max_bytes = size * nmemb; - - /* clamp for virtual filesize */ - if (max_bytes > ov_streamfile->size - ov_streamfile->offset) - max_bytes = ov_streamfile->size - ov_streamfile->offset; - - bytes_read = read_streamfile(ptr, real_offset, max_bytes, ov_streamfile->streamfile); - items_read = bytes_read / size; - - /* may be encrypted */ - if (ov_streamfile->decryption_callback) { - ov_streamfile->decryption_callback(ptr, size, items_read, ov_streamfile); - } - - ov_streamfile->offset += items_read * size; - - return items_read; -} - -static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) { - ogg_vorbis_streamfile * const ov_streamfile = datasource; - ogg_int64_t base_offset, new_offset; - - switch (whence) { - case SEEK_SET: - base_offset = 0; - break; - case SEEK_CUR: - base_offset = ov_streamfile->offset; - break; - case SEEK_END: - base_offset = ov_streamfile->size; - break; - default: - return -1; - break; - } - - - new_offset = base_offset + offset; - if (new_offset < 0 || new_offset > ov_streamfile->size) { - return -1; /* *must* return -1 if stream is unseekable */ - } else { - ov_streamfile->offset = new_offset; - return 0; - } -} - -static long ov_tell_func(void * datasource) { - ogg_vorbis_streamfile * const ov_streamfile = datasource; - return ov_streamfile->offset; -} - -static int ov_close_func(void * datasource) { - /* needed as setting ov_close_func in ov_callbacks to NULL doesn't seem to work - * (closing the streamfile is done in free_ogg_vorbis) */ - return 0; -} - static void um3_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { - size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile * const ov_streamfile = datasource; + uint8_t *ptr8 = ptr; + size_t bytes_read = size * nmemb; + ogg_vorbis_io *io = datasource; int i; /* first 0x800 bytes are xor'd */ - if (ov_streamfile->offset < 0x800) { - int num_crypt = 0x800 - ov_streamfile->offset; + if (io->offset < 0x800) { + int num_crypt = 0x800 - io->offset; if (num_crypt > bytes_read) num_crypt = bytes_read; for (i = 0; i < num_crypt; i++) - ((uint8_t*)ptr)[i] ^= 0xff; + ptr8[i] ^= 0xff; } } static void kovs_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { - size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile * const ov_streamfile = datasource; + uint8_t *ptr8 = ptr; + size_t bytes_read = size * nmemb; + ogg_vorbis_io *io = datasource; int i; /* first 0x100 bytes are xor'd */ - if (ov_streamfile->offset < 0x100) { - int max_offset = ov_streamfile->offset + bytes_read; + if (io->offset < 0x100) { + int max_offset = io->offset + bytes_read; if (max_offset > 0x100) max_offset = 0x100; - for (i = ov_streamfile->offset; i < max_offset; i++) { - ((uint8_t*)ptr)[i-ov_streamfile->offset] ^= i; + for (i = io->offset; i < max_offset; i++) { + ptr8[i-io->offset] ^= i; } } } static void psychic_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { - ogg_vorbis_streamfile * const ov_streamfile = datasource; - size_t bytes_read = size*nmemb; - uint8_t key[6] = { 0x23,0x31,0x20,0x2e,0x2e,0x28 }; + static const uint8_t key[6] = { + 0x23,0x31,0x20,0x2e,0x2e,0x28 + }; + uint8_t *ptr8 = ptr; + size_t bytes_read = size * nmemb; + ogg_vorbis_io *io = datasource; int i; //todo incorrect, picked value changes (fixed order for all files), or key is bigger /* bytes add key that changes every 0x64 bytes */ for (i = 0; i < bytes_read; i++) { - int pos = (ov_streamfile->offset + i) / 0x64; - ((uint8_t*)ptr)[i] += key[pos % sizeof(key)]; + int pos = (io->offset + i) / 0x64; + ptr8[i] += key[pos % sizeof(key)]; } } @@ -125,21 +62,22 @@ static void rpgmvo_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, static const uint8_t header[16] = { /* OggS, packet type, granule, stream id(empty) */ 0x4F,0x67,0x67,0x53,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; + uint8_t *ptr8 = ptr; size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile * const ov_streamfile = datasource; + ogg_vorbis_io *io = datasource; int i; /* first 0x10 are xor'd, but header can be easily reconstructed * (key is also in (game)/www/data/System.json "encryptionKey") */ for (i = 0; i < bytes_read; i++) { - if (ov_streamfile->offset+i < 0x10) { - ((uint8_t*)ptr)[i] = header[(ov_streamfile->offset + i) % 16]; + if (io->offset+i < 0x10) { + ptr8[i] = header[(io->offset + i) % 16]; /* last two bytes are the stream id, get from next OggS */ - if (ov_streamfile->offset+i == 0x0e) - ((uint8_t*)ptr)[i] = read_8bit(0x58, ov_streamfile->streamfile); - if (ov_streamfile->offset+i == 0x0f) - ((uint8_t*)ptr)[i] = read_8bit(0x59, ov_streamfile->streamfile); + if (io->offset+i == 0x0e) + ptr8[i] = read_8bit(0x58, io->streamfile); + if (io->offset+i == 0x0f) + ptr8[i] = read_8bit(0x59, io->streamfile); } } } @@ -158,7 +96,7 @@ static const uint32_t xiph_mappings[] = { }; -/* Ogg Vorbis, by way of libvorbisfile; may contain loop comments */ +/* Ogg Vorbis, may contain loop comments */ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; STREAMFILE *temp_streamFile = NULL; @@ -181,7 +119,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { /* check extension */ /* .ogg: standard/various, .logg: renamed for plugins - * .adx: KID [Remember11 (PC)] + * .adx: KID games [Remember11 (PC)] * .rof: The Rhythm of Fighters (Mobile) * .acm: Planescape Torment Enhanced Edition (PC) * .sod: Zone 4 (PC) @@ -190,7 +128,8 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { is_ogg = 1; } else if (check_extensions(streamFile,"um3")) { is_um3 = 1; - } else if (check_extensions(streamFile,"kvs,kovs")) { /* .kvs: Atelier Sophie (PC), kovs: header id only? */ + } else if (check_extensions(streamFile,"kvs,kovs")) { + /* .kvs: Atelier Sophie (PC), kovs: header id only? */ is_kovs = 1; } else if (check_extensions(streamFile,"sngw")) { /* .sngw: Capcom [Devil May Cry 4 SE (PC), Biohazard 6 (PC)] */ is_sngw = 1; @@ -228,11 +167,11 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { } else if (read_32bitBE(0x00,streamFile) == 0x00000000 && /* null instead of "OggS" [Yuppie Psycho (PC)] */ - read_32bitBE(0x3a,streamFile) == 0x4F676753) { + read_32bitBE(0x3a,streamFile) == 0x4F676753) { /* "OggS" in next page */ cfg.is_header_swap = 1; cfg.is_encrypted = 1; } - else if (read_32bitBE(0x00,streamFile) == 0x4f676753) { /* "OggS" (standard) */ + else if (read_32bitBE(0x00,streamFile) == 0x4F676753) { /* "OggS" (standard) */ ; } else { @@ -382,7 +321,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { if (is_eno) { /* [Metronomicon (PC)] */ /* first byte probably derives into key, but this works too */ - cfg.key[0] = (uint8_t)read_8bit(0x05,streamFile); /* regular ogg have a zero at this offset = easy key */; + cfg.key[0] = (uint8_t)read_8bit(0x05,streamFile); /* regular ogg have a zero at this offset = easy key */ cfg.key_len = 1; cfg.is_encrypted = 1; start_offset = 0x01; /* "OggS" starts after key-thing */ @@ -394,7 +333,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { cfg.is_encrypted = 1; } - if (is_mus) { /* [Redux - Dark Matters (PC)] */ + if (is_mus) { /* [Redux: Dark Matters (PC)] */ static const uint8_t mus_key[16] = { 0x21,0x4D,0x6F,0x01,0x20,0x4C,0x6E,0x02,0x1F,0x4B,0x6D,0x03,0x20,0x4C,0x6E,0x02 }; @@ -464,12 +403,12 @@ fail: return NULL; } -VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callbacks *callbacks_p, off_t start, const ogg_vorbis_meta_info_t *ovmi) { +VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callbacks *callbacks, off_t start, const ogg_vorbis_meta_info_t *ovmi) { VGMSTREAM * vgmstream = NULL; - ogg_vorbis_codec_data * data = NULL; - OggVorbis_File *ovf = NULL; - vorbis_info *vi; + ogg_vorbis_codec_data* data = NULL; + ogg_vorbis_io io = {0}; char name[STREAM_NAME_SIZE] = {0}; + int channels, sample_rate, num_samples; int loop_flag = ovmi->loop_flag; int32_t loop_start = ovmi->loop_start; @@ -480,156 +419,96 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb size_t stream_size = ovmi->stream_size ? ovmi->stream_size : get_streamfile_size(streamFile) - start; - - ov_callbacks default_callbacks; - - if (!callbacks_p) { - default_callbacks.read_func = ov_read_func; - default_callbacks.seek_func = ov_seek_func; - default_callbacks.close_func = ov_close_func; - default_callbacks.tell_func = ov_tell_func; - - callbacks_p = &default_callbacks; - } - - /* test if this is a proper Ogg Vorbis file, with the current (from init_x) STREAMFILE */ - { - OggVorbis_File temp_ovf = {0}; - ogg_vorbis_streamfile temp_streamfile = {0}; - - temp_streamfile.streamfile = streamFile; - - temp_streamfile.start = start; - temp_streamfile.offset = 0; - temp_streamfile.size = stream_size; - - temp_streamfile.decryption_callback = ovmi->decryption_callback; - temp_streamfile.scd_xor = ovmi->scd_xor; - temp_streamfile.scd_xor_length = ovmi->scd_xor_length; - temp_streamfile.xor_value = ovmi->xor_value; - - /* open the ogg vorbis file for testing */ - if (ov_test_callbacks(&temp_streamfile, &temp_ovf, NULL, 0, *callbacks_p)) - goto fail; - - /* we have to close this as it has the init_vgmstream meta-reading STREAMFILE */ - ov_clear(&temp_ovf); - } + int disable_reordering = ovmi->disable_reordering; - /* proceed to init codec_data and reopen a STREAMFILE for this stream */ - { - char filename[PATH_LIMIT]; + //todo improve how to pass config + io.decryption_callback = ovmi->decryption_callback; + io.scd_xor = ovmi->scd_xor; + io.scd_xor_length = ovmi->scd_xor_length; + io.xor_value = ovmi->xor_value; - data = calloc(1,sizeof(ogg_vorbis_codec_data)); - if (!data) goto fail; - - streamFile->get_name(streamFile,filename,sizeof(filename)); - data->ov_streamfile.streamfile = streamFile->open(streamFile,filename, STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!data->ov_streamfile.streamfile) goto fail; - - data->ov_streamfile.start = start; - data->ov_streamfile.offset = 0; - data->ov_streamfile.size = stream_size; - - data->ov_streamfile.decryption_callback = ovmi->decryption_callback; - data->ov_streamfile.scd_xor = ovmi->scd_xor; - data->ov_streamfile.scd_xor_length = ovmi->scd_xor_length; - data->ov_streamfile.xor_value = ovmi->xor_value; - - /* open the ogg vorbis file for real */ - if (ov_open_callbacks(&data->ov_streamfile, &data->ogg_vorbis_file, NULL, 0, *callbacks_p)) - goto fail; - ovf = &data->ogg_vorbis_file; - } - - //todo could set bitstreams as subsongs? - /* get info from bitstream 0 */ - data->bitstream = OGG_DEFAULT_BITSTREAM; - vi = ov_info(ovf,OGG_DEFAULT_BITSTREAM); - - /* other settings */ - data->disable_reordering = ovmi->disable_reordering; + data = init_ogg_vorbis(streamFile, start, stream_size, &io); + if (!data) goto fail; /* search for loop comments */ {//todo ignore if loop flag already set? - int i; - vorbis_comment *comment = ov_comment(ovf,OGG_DEFAULT_BITSTREAM); + const char * comment = NULL; - for (i = 0; i < comment->comments; i++) { - const char * user_comment = comment->user_comments[i]; - if (strstr(user_comment,"loop_start=")==user_comment || /* PSO4 */ - strstr(user_comment,"LOOP_START=")==user_comment || /* PSO4 */ - strstr(user_comment,"COMMENT=LOOPPOINT=")==user_comment || - strstr(user_comment,"LOOPSTART=")==user_comment || - strstr(user_comment,"um3.stream.looppoint.start=")==user_comment || - strstr(user_comment,"LOOP_BEGIN=")==user_comment || /* Hatsune Miku: Project Diva F (PS3) */ - strstr(user_comment,"LoopStart=")==user_comment || /* Devil May Cry 4 (PC) */ - strstr(user_comment,"XIPH_CUE_LOOPSTART=")==user_comment) { /* Super Mario Run (Android) */ - loop_start = atol(strrchr(user_comment,'=')+1); + while (ogg_vorbis_get_comment(data, &comment)) { + + if (strstr(comment,"loop_start=") == comment || /* PSO4 */ + strstr(comment,"LOOP_START=") == comment || /* PSO4 */ + strstr(comment,"COMMENT=LOOPPOINT=") == comment || + strstr(comment,"LOOPSTART=") == comment || + strstr(comment,"um3.stream.looppoint.start=") == comment || + strstr(comment,"LOOP_BEGIN=") == comment || /* Hatsune Miku: Project Diva F (PS3) */ + strstr(comment,"LoopStart=") == comment || /* Devil May Cry 4 (PC) */ + strstr(comment,"XIPH_CUE_LOOPSTART=") == comment) { /* Super Mario Run (Android) */ + loop_start = atol(strrchr(comment,'=')+1); loop_flag = (loop_start >= 0); } - else if (strstr(user_comment,"LOOPLENGTH=")==user_comment) {/* (LOOPSTART pair) */ - loop_length = atol(strrchr(user_comment,'=')+1); + else if (strstr(comment,"LOOPLENGTH=") == comment) {/* (LOOPSTART pair) */ + loop_length = atol(strrchr(comment,'=')+1); loop_length_found = 1; } - else if (strstr(user_comment,"title=-lps")==user_comment) { /* KID [Memories Off #5 (PC), Remember11 (PC)] */ - loop_start = atol(user_comment+10); + else if (strstr(comment,"title=-lps") == comment) { /* KID [Memories Off #5 (PC), Remember11 (PC)] */ + loop_start = atol(comment+10); loop_flag = (loop_start >= 0); } - else if (strstr(user_comment,"album=-lpe")==user_comment) { /* (title=-lps pair) */ - loop_end = atol(user_comment+10); + else if (strstr(comment,"album=-lpe") == comment) { /* (title=-lps pair) */ + loop_end = atol(comment+10); loop_flag = 1; loop_end_found = 1; } - else if (strstr(user_comment,"LoopEnd=")==user_comment) { /* (LoopStart pair) */ + else if (strstr(comment,"LoopEnd=") == comment) { /* (LoopStart pair) */ if(loop_flag) { - loop_length = atol(strrchr(user_comment,'=')+1)-loop_start; + loop_length = atol(strrchr(comment,'=')+1)-loop_start; loop_length_found = 1; } } - else if (strstr(user_comment,"LOOP_END=")==user_comment) { /* (LOOP_BEGIN pair) */ + else if (strstr(comment,"LOOP_END=") == comment) { /* (LOOP_BEGIN pair) */ if(loop_flag) { - loop_length = atol(strrchr(user_comment,'=')+1)-loop_start; + loop_length = atol(strrchr(comment,'=')+1)-loop_start; loop_length_found = 1; } } - else if (strstr(user_comment,"lp=")==user_comment) { - sscanf(strrchr(user_comment,'=')+1,"%d,%d", &loop_start,&loop_end); + else if (strstr(comment,"lp=") == comment) { + sscanf(strrchr(comment,'=')+1,"%d,%d", &loop_start,&loop_end); loop_flag = 1; loop_end_found = 1; } - else if (strstr(user_comment,"LOOPDEFS=")==user_comment) { /* Fairy Fencer F: Advent Dark Force */ - sscanf(strrchr(user_comment,'=')+1,"%d,%d", &loop_start,&loop_end); + else if (strstr(comment,"LOOPDEFS=") == comment) { /* Fairy Fencer F: Advent Dark Force */ + sscanf(strrchr(comment,'=')+1,"%d,%d", &loop_start,&loop_end); loop_flag = 1; loop_end_found = 1; } - else if (strstr(user_comment,"COMMENT=loop(")==user_comment) { /* Zero Time Dilemma (PC) */ - sscanf(strrchr(user_comment,'(')+1,"%d,%d", &loop_start,&loop_end); + else if (strstr(comment,"COMMENT=loop(") == comment) { /* Zero Time Dilemma (PC) */ + sscanf(strrchr(comment,'(')+1,"%d,%d", &loop_start,&loop_end); loop_flag = 1; loop_end_found = 1; } - else if (strstr(user_comment, "XIPH_CUE_LOOPEND=") == user_comment) { /* XIPH_CUE_LOOPSTART pair */ + else if (strstr(comment, "XIPH_CUE_LOOPEND=") == comment) { /* (XIPH_CUE_LOOPSTART pair) */ if (loop_flag) { - loop_length = atol(strrchr(user_comment, '=') + 1) - loop_start; + loop_length = atol(strrchr(comment, '=') + 1) - loop_start; loop_length_found = 1; } } - else if (strstr(user_comment, "omment=") == user_comment) { /* Air (Android) */ - sscanf(strstr(user_comment, "=LOOPSTART=") + 11, "%d,LOOPEND=%d", &loop_start, &loop_end); + else if (strstr(comment, "omment=") == comment) { /* Air (Android) */ + sscanf(strstr(comment, "=LOOPSTART=") + 11, "%d,LOOPEND=%d", &loop_start, &loop_end); loop_flag = 1; loop_end_found = 1; } - else if (strstr(user_comment,"MarkerNum=0002")==user_comment) { /* Megaman X Legacy Collection: MMX1/2/3 (PC) flag */ + else if (strstr(comment,"MarkerNum=0002") == comment) { /* Megaman X Legacy Collection: MMX1/2/3 (PC) flag */ /* uses LoopStart=-1 LoopEnd=-1, then 3 secuential comments: "MarkerNum" + "M=7F(start)" + "M=7F(end)" */ loop_flag = 1; } - else if (strstr(user_comment,"M=7F")==user_comment) { /* Megaman X Legacy Collection: MMX1/2/3 (PC) start/end */ + else if (strstr(comment,"M=7F") == comment) { /* Megaman X Legacy Collection: MMX1/2/3 (PC) start/end */ if (loop_flag && loop_start < 0) { /* LoopStart should set as -1 before */ - sscanf(user_comment,"M=7F%x", &loop_start); + sscanf(comment,"M=7F%x", &loop_start); } else if (loop_flag && loop_start >= 0) { - sscanf(user_comment,"M=7F%x", &loop_end); + sscanf(comment,"M=7F%x", &loop_end); loop_end_found = 1; } } @@ -637,26 +516,33 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb /* Hatsune Miku Project DIVA games, though only 'Arcade Future Tone' has >4ch files * ENCODER tag is common but ogg_vorbis_encode looks unique enough * (arcade ends with "2010-11-26" while consoles have "2011-02-07" */ - if (strstr(user_comment, "ENCODER=ogg_vorbis_encode/") == user_comment) { - data->disable_reordering = 1; + if (strstr(comment, "ENCODER=ogg_vorbis_encode/") == comment) { + disable_reordering = 1; } - if (strstr(user_comment, "TITLE=") == user_comment) { - strncpy(name, user_comment + 6, sizeof(name) - 1); + if (strstr(comment, "TITLE=") == comment) { + strncpy(name, comment + 6, sizeof(name) - 1); } - ;VGM_LOG("OGG: user_comment=%s\n", user_comment); + ;VGM_LOG("OGG: user_comment=%s\n", comment); } } + ogg_vorbis_set_disable_reordering(data, disable_reordering); + ogg_vorbis_get_info(data, &channels, &sample_rate); + ogg_vorbis_get_samples(data, &num_samples); /* let libvorbisfile find total samples */ + /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(vi->channels,loop_flag); + vgmstream = allocate_vgmstream(channels,loop_flag); if (!vgmstream) goto fail; - vgmstream->codec_data = data; /* store our fun extra datas */ - vgmstream->channels = vi->channels; - vgmstream->sample_rate = vi->rate; + vgmstream->codec_data = data; + vgmstream->coding_type = coding_OGG_VORBIS; + vgmstream->layout_type = layout_none; + vgmstream->meta_type = ovmi->meta_type; + + vgmstream->sample_rate = sample_rate; vgmstream->stream_size = stream_size; if (ovmi->total_subsongs) /* not setting it has some effect when showing stream names */ @@ -665,11 +551,11 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb if (name[0] != '\0') strcpy(vgmstream->stream_name, name); - vgmstream->num_samples = ov_pcm_total(ovf,-1); /* let libvorbisfile find total samples */ + vgmstream->num_samples = num_samples; if (loop_flag) { vgmstream->loop_start_sample = loop_start; if (loop_length_found) - vgmstream->loop_end_sample = loop_start+loop_length; + vgmstream->loop_end_sample = loop_start + loop_length; else if (loop_end_found) vgmstream->loop_end_sample = loop_end; else @@ -679,10 +565,6 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb vgmstream->loop_end_sample = vgmstream->num_samples; } - vgmstream->coding_type = coding_OGG_VORBIS; - vgmstream->layout_type = layout_none; - vgmstream->meta_type = ovmi->meta_type; - if (vgmstream->channels <= 8) { vgmstream->channel_layout = xiph_mappings[vgmstream->channels]; } @@ -690,18 +572,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb return vgmstream; fail: - /* clean up anything we may have opened */ - if (data) { - if (ovf) - ov_clear(&data->ogg_vorbis_file);//same as ovf - if (data->ov_streamfile.streamfile) - close_streamfile(data->ov_streamfile.streamfile); - free(data); - } - if (vgmstream) { - vgmstream->codec_data = NULL; - close_vgmstream(vgmstream); - } + close_vgmstream(vgmstream); return NULL; } diff --git a/src/meta/sqex_scd.c b/src/meta/sqex_scd.c index e17e803d..9d99ae04 100644 --- a/src/meta/sqex_scd.c +++ b/src/meta/sqex_scd.c @@ -416,23 +416,24 @@ fail: #ifdef VGM_USE_VORBIS static void scd_ogg_v2_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) { - size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile * ov_streamfile = (ogg_vorbis_streamfile*)datasource; + uint8_t *ptr8 = ptr; + size_t bytes_read = size * nmemb; + ogg_vorbis_io *io = datasource; /* no encryption, sometimes happens */ - if (ov_streamfile->scd_xor == 0x00) + if (io->scd_xor == 0x00) return; /* header is XOR'd with a constant byte */ - if (ov_streamfile->offset < ov_streamfile->scd_xor_length) { + if (io->offset < io->scd_xor_length) { int i, num_crypt; - num_crypt = ov_streamfile->scd_xor_length - ov_streamfile->offset; + num_crypt = io->scd_xor_length - io->offset; if (num_crypt > bytes_read) num_crypt = bytes_read; for (i = 0; i < num_crypt; i++) { - ((uint8_t*)ptr)[i] ^= (uint8_t)ov_streamfile->scd_xor; + ptr8[i] ^= (uint8_t)io->scd_xor; } } } @@ -457,25 +458,25 @@ static void scd_ogg_v3_decryption_callback(void *ptr, size_t size, size_t nmemb, 0xE2, 0xA2, 0x67, 0x32, 0x32, 0x12, 0x32, 0xB2, 0x32, 0x32, 0x32, 0x32, 0x75, 0xA3, 0x26, 0x7B, // E0-EF 0x83, 0x26, 0xF9, 0x83, 0x2E, 0xFF, 0xE3, 0x16, 0x7D, 0xC0, 0x1E, 0x63, 0x21, 0x07, 0xE3, 0x01, // F0-FF }; - - size_t bytes_read = size*nmemb; - ogg_vorbis_streamfile *ov_streamfile = (ogg_vorbis_streamfile*)datasource; + uint8_t *ptr8 = ptr; + size_t bytes_read = size * nmemb; + ogg_vorbis_io *io = datasource; /* file is XOR'd with a table (algorithm and table by Ioncannon) */ - { //if (ov_streamfile->offset < ov_streamfile->scd_xor_length) + { //if (io->offset < io->scd_xor_length) int i, num_crypt; uint8_t byte1, byte2, xor_byte; num_crypt = bytes_read; - byte1 = ov_streamfile->scd_xor & 0x7F; - byte2 = ov_streamfile->scd_xor & 0x3F; + byte1 = io->scd_xor & 0x7F; + byte2 = io->scd_xor & 0x3F; for (i = 0; i < num_crypt; i++) { - xor_byte = scd_ogg_v3_lookuptable[(byte2 + ov_streamfile->offset + i) & 0xFF]; + xor_byte = scd_ogg_v3_lookuptable[(byte2 + io->offset + i) & 0xFF]; xor_byte &= 0xFF; - xor_byte ^= ((uint8_t*)ptr)[i]; + xor_byte ^= ptr8[i]; xor_byte ^= byte1; - ((uint8_t*)ptr)[i] = (uint8_t)xor_byte; + ptr8[i] = xor_byte; } } } diff --git a/src/vgmstream.c b/src/vgmstream.c index 233472ea..f13b7c0e 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -2628,8 +2628,7 @@ static STREAMFILE * get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM * #ifdef VGM_USE_VORBIS if (vgmstream->coding_type == coding_OGG_VORBIS) { - ogg_vorbis_codec_data *data = vgmstream->codec_data; - return data ? data->ov_streamfile.streamfile : NULL; + return ogg_vorbis_get_streamfile(vgmstream->codec_data); } #endif if (vgmstream->coding_type == coding_CRI_HCA) { diff --git a/src/vgmstream.h b/src/vgmstream.h index 1badfdbe..44c15930 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -897,7 +897,8 @@ typedef struct { } VGMSTREAM; #ifdef VGM_USE_VORBIS -/* Ogg with Vorbis */ + +/* standard Ogg Vorbis */ typedef struct { STREAMFILE *streamfile; ogg_int64_t start; /* file offset where the Ogg starts */ @@ -910,15 +911,9 @@ typedef struct { off_t scd_xor_length; uint32_t xor_value; -} ogg_vorbis_streamfile; +} ogg_vorbis_io; -typedef struct { - OggVorbis_File ogg_vorbis_file; - int bitstream; - - ogg_vorbis_streamfile ov_streamfile; - int disable_reordering; /* Xiph reorder channels on output, except for some devs */ -} ogg_vorbis_codec_data; +typedef struct ogg_vorbis_codec_data ogg_vorbis_codec_data; /* custom Vorbis modes */