mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 16:30:54 +01:00
Allow Ogg decoder to be used like other codecs
This commit is contained in:
parent
2b1de051e2
commit
15d794bbe2
@ -212,11 +212,18 @@ int test_hca_key(hca_codec_data * data, unsigned long long keycode);
|
|||||||
|
|
||||||
#ifdef VGM_USE_VORBIS
|
#ifdef VGM_USE_VORBIS
|
||||||
/* ogg_vorbis_decoder */
|
/* 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 reset_ogg_vorbis(VGMSTREAM *vgmstream);
|
||||||
void seek_ogg_vorbis(VGMSTREAM *vgmstream, int32_t num_sample);
|
void seek_ogg_vorbis(VGMSTREAM *vgmstream, int32_t num_sample);
|
||||||
void free_ogg_vorbis(ogg_vorbis_codec_data *data);
|
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_decoder */
|
||||||
vorbis_custom_codec_data *init_vorbis_custom(STREAMFILE *streamfile, off_t start_offset, vorbis_custom_t type, vorbis_custom_config * config);
|
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);
|
void decode_vorbis_custom(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels);
|
||||||
|
@ -5,21 +5,183 @@
|
|||||||
#ifdef VGM_USE_VORBIS
|
#ifdef VGM_USE_VORBIS
|
||||||
#include <vorbis/vorbisfile.h>
|
#include <vorbis/vorbisfile.h>
|
||||||
|
|
||||||
|
#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;
|
int samples_done = 0;
|
||||||
long rc;
|
long rc;
|
||||||
float **pcm_channels; /* pointer to Xiph's double array buffer */
|
float **pcm_channels; /* pointer to Xiph's double array buffer */
|
||||||
|
|
||||||
while (samples_done < samples_to_do) {
|
while (samples_done < samples_to_do) {
|
||||||
rc = ov_read_float(
|
rc = ov_read_float(
|
||||||
&data->ogg_vorbis_file, /* context */
|
&data->ogg_vorbis_file, /* context */
|
||||||
&pcm_channels, /* buffer pointer */
|
&pcm_channels, /* buffer pointer */
|
||||||
(samples_to_do - samples_done), /* samples to produce */
|
(samples_to_do - samples_done), /* samples to produce */
|
||||||
&data->bitstream); /* bitstream*/
|
&data->bitstream); /* bitstream */
|
||||||
if (rc <= 0) goto fail; /* rc is samples done */
|
if (rc <= 0) goto fail; /* rc is samples done */
|
||||||
|
|
||||||
pcm_convert_float_to_16(channels, outbuf, rc, pcm_channels, data->disable_reordering);
|
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,
|
/* 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. */
|
* but seems ov_read is slightly faster due to optimized (asm) float-to-int. */
|
||||||
rc = ov_read(
|
rc = ov_read(
|
||||||
&data->ogg_vorbis_file, /* context */
|
&data->ogg_vorbis_file, /* context */
|
||||||
(char *)(outbuf), /* buffer */
|
(char *)(outbuf), /* buffer */
|
||||||
(samples_to_do - samples_done) * sizeof(sample_t) * channels, /* length in bytes */
|
(samples_to_do - samples_done) * sizeof(sample_t) * channels, /* length in bytes */
|
||||||
0, /* pcm endianness */
|
0, /* pcm endianness */
|
||||||
sizeof(sample), /* pcm size */
|
sizeof(sample), /* pcm size */
|
||||||
1, /* pcm signedness */
|
1, /* pcm signedness */
|
||||||
&data->bitstream); /* bitstream */
|
&data->bitstream); /* bitstream */
|
||||||
if (rc <= 0) goto fail; /* rc is bytes done (for all channels) */
|
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 */
|
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.
|
/* 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
|
* (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] = {
|
static const int xiph_channel_map[8][8] = {
|
||||||
{ 0 }, /* 1ch: FC > same */
|
{ 0 }, /* 1ch: FC > same */
|
||||||
{ 0, 1 }, /* 2ch: FL FR > 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) {
|
void free_ogg_vorbis(ogg_vorbis_codec_data *data) {
|
||||||
if (!data) return;
|
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);
|
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
|
#endif
|
||||||
|
@ -128,7 +128,7 @@ typedef struct {
|
|||||||
|
|
||||||
} ogg_vorbis_meta_info_t;
|
} 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
|
#endif
|
||||||
|
|
||||||
VGMSTREAM * init_vgmstream_hca(STREAMFILE *streamFile);
|
VGMSTREAM * init_vgmstream_hca(STREAMFILE *streamFile);
|
||||||
|
@ -1,123 +1,60 @@
|
|||||||
#include "../vgmstream.h"
|
|
||||||
|
|
||||||
#ifdef VGM_USE_VORBIS
|
#ifdef VGM_USE_VORBIS
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <vorbis/vorbisfile.h>
|
|
||||||
#include "meta.h"
|
#include "meta.h"
|
||||||
|
#include "../coding/coding.h"
|
||||||
#include "ogg_vorbis_streamfile.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) {
|
static void um3_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
||||||
size_t bytes_read = size*nmemb;
|
uint8_t *ptr8 = ptr;
|
||||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
size_t bytes_read = size * nmemb;
|
||||||
|
ogg_vorbis_io *io = datasource;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* first 0x800 bytes are xor'd */
|
/* first 0x800 bytes are xor'd */
|
||||||
if (ov_streamfile->offset < 0x800) {
|
if (io->offset < 0x800) {
|
||||||
int num_crypt = 0x800 - ov_streamfile->offset;
|
int num_crypt = 0x800 - io->offset;
|
||||||
if (num_crypt > bytes_read)
|
if (num_crypt > bytes_read)
|
||||||
num_crypt = bytes_read;
|
num_crypt = bytes_read;
|
||||||
|
|
||||||
for (i = 0; i < num_crypt; i++)
|
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) {
|
static void kovs_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
||||||
size_t bytes_read = size*nmemb;
|
uint8_t *ptr8 = ptr;
|
||||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
size_t bytes_read = size * nmemb;
|
||||||
|
ogg_vorbis_io *io = datasource;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* first 0x100 bytes are xor'd */
|
/* first 0x100 bytes are xor'd */
|
||||||
if (ov_streamfile->offset < 0x100) {
|
if (io->offset < 0x100) {
|
||||||
int max_offset = ov_streamfile->offset + bytes_read;
|
int max_offset = io->offset + bytes_read;
|
||||||
if (max_offset > 0x100)
|
if (max_offset > 0x100)
|
||||||
max_offset = 0x100;
|
max_offset = 0x100;
|
||||||
|
|
||||||
for (i = ov_streamfile->offset; i < max_offset; i++) {
|
for (i = io->offset; i < max_offset; i++) {
|
||||||
((uint8_t*)ptr)[i-ov_streamfile->offset] ^= i;
|
ptr8[i-io->offset] ^= i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void psychic_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
static void psychic_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
||||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
static const uint8_t key[6] = {
|
||||||
size_t bytes_read = size*nmemb;
|
0x23,0x31,0x20,0x2e,0x2e,0x28
|
||||||
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;
|
int i;
|
||||||
|
|
||||||
//todo incorrect, picked value changes (fixed order for all files), or key is bigger
|
//todo incorrect, picked value changes (fixed order for all files), or key is bigger
|
||||||
/* bytes add key that changes every 0x64 bytes */
|
/* bytes add key that changes every 0x64 bytes */
|
||||||
for (i = 0; i < bytes_read; i++) {
|
for (i = 0; i < bytes_read; i++) {
|
||||||
int pos = (ov_streamfile->offset + i) / 0x64;
|
int pos = (io->offset + i) / 0x64;
|
||||||
((uint8_t*)ptr)[i] += key[pos % sizeof(key)];
|
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) */
|
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
|
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;
|
size_t bytes_read = size*nmemb;
|
||||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
ogg_vorbis_io *io = datasource;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* first 0x10 are xor'd, but header can be easily reconstructed
|
/* first 0x10 are xor'd, but header can be easily reconstructed
|
||||||
* (key is also in (game)/www/data/System.json "encryptionKey") */
|
* (key is also in (game)/www/data/System.json "encryptionKey") */
|
||||||
for (i = 0; i < bytes_read; i++) {
|
for (i = 0; i < bytes_read; i++) {
|
||||||
if (ov_streamfile->offset+i < 0x10) {
|
if (io->offset+i < 0x10) {
|
||||||
((uint8_t*)ptr)[i] = header[(ov_streamfile->offset + i) % 16];
|
ptr8[i] = header[(io->offset + i) % 16];
|
||||||
|
|
||||||
/* last two bytes are the stream id, get from next OggS */
|
/* last two bytes are the stream id, get from next OggS */
|
||||||
if (ov_streamfile->offset+i == 0x0e)
|
if (io->offset+i == 0x0e)
|
||||||
((uint8_t*)ptr)[i] = read_8bit(0x58, ov_streamfile->streamfile);
|
ptr8[i] = read_8bit(0x58, io->streamfile);
|
||||||
if (ov_streamfile->offset+i == 0x0f)
|
if (io->offset+i == 0x0f)
|
||||||
((uint8_t*)ptr)[i] = read_8bit(0x59, ov_streamfile->streamfile);
|
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 * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
||||||
VGMSTREAM * vgmstream = NULL;
|
VGMSTREAM * vgmstream = NULL;
|
||||||
STREAMFILE *temp_streamFile = NULL;
|
STREAMFILE *temp_streamFile = NULL;
|
||||||
@ -181,7 +119,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
|||||||
|
|
||||||
/* check extension */
|
/* check extension */
|
||||||
/* .ogg: standard/various, .logg: renamed for plugins
|
/* .ogg: standard/various, .logg: renamed for plugins
|
||||||
* .adx: KID [Remember11 (PC)]
|
* .adx: KID games [Remember11 (PC)]
|
||||||
* .rof: The Rhythm of Fighters (Mobile)
|
* .rof: The Rhythm of Fighters (Mobile)
|
||||||
* .acm: Planescape Torment Enhanced Edition (PC)
|
* .acm: Planescape Torment Enhanced Edition (PC)
|
||||||
* .sod: Zone 4 (PC)
|
* .sod: Zone 4 (PC)
|
||||||
@ -190,7 +128,8 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
|||||||
is_ogg = 1;
|
is_ogg = 1;
|
||||||
} else if (check_extensions(streamFile,"um3")) {
|
} else if (check_extensions(streamFile,"um3")) {
|
||||||
is_um3 = 1;
|
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;
|
is_kovs = 1;
|
||||||
} else if (check_extensions(streamFile,"sngw")) { /* .sngw: Capcom [Devil May Cry 4 SE (PC), Biohazard 6 (PC)] */
|
} else if (check_extensions(streamFile,"sngw")) { /* .sngw: Capcom [Devil May Cry 4 SE (PC), Biohazard 6 (PC)] */
|
||||||
is_sngw = 1;
|
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)] */
|
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_header_swap = 1;
|
||||||
cfg.is_encrypted = 1;
|
cfg.is_encrypted = 1;
|
||||||
}
|
}
|
||||||
else if (read_32bitBE(0x00,streamFile) == 0x4f676753) { /* "OggS" (standard) */
|
else if (read_32bitBE(0x00,streamFile) == 0x4F676753) { /* "OggS" (standard) */
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -382,7 +321,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
|||||||
|
|
||||||
if (is_eno) { /* [Metronomicon (PC)] */
|
if (is_eno) { /* [Metronomicon (PC)] */
|
||||||
/* first byte probably derives into key, but this works too */
|
/* 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.key_len = 1;
|
||||||
cfg.is_encrypted = 1;
|
cfg.is_encrypted = 1;
|
||||||
start_offset = 0x01; /* "OggS" starts after key-thing */
|
start_offset = 0x01; /* "OggS" starts after key-thing */
|
||||||
@ -394,7 +333,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
|||||||
cfg.is_encrypted = 1;
|
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] = {
|
static const uint8_t mus_key[16] = {
|
||||||
0x21,0x4D,0x6F,0x01,0x20,0x4C,0x6E,0x02,0x1F,0x4B,0x6D,0x03,0x20,0x4C,0x6E,0x02
|
0x21,0x4D,0x6F,0x01,0x20,0x4C,0x6E,0x02,0x1F,0x4B,0x6D,0x03,0x20,0x4C,0x6E,0x02
|
||||||
};
|
};
|
||||||
@ -464,12 +403,12 @@ fail:
|
|||||||
return NULL;
|
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;
|
VGMSTREAM * vgmstream = NULL;
|
||||||
ogg_vorbis_codec_data * data = NULL;
|
ogg_vorbis_codec_data* data = NULL;
|
||||||
OggVorbis_File *ovf = NULL;
|
ogg_vorbis_io io = {0};
|
||||||
vorbis_info *vi;
|
|
||||||
char name[STREAM_NAME_SIZE] = {0};
|
char name[STREAM_NAME_SIZE] = {0};
|
||||||
|
int channels, sample_rate, num_samples;
|
||||||
|
|
||||||
int loop_flag = ovmi->loop_flag;
|
int loop_flag = ovmi->loop_flag;
|
||||||
int32_t loop_start = ovmi->loop_start;
|
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 ?
|
size_t stream_size = ovmi->stream_size ?
|
||||||
ovmi->stream_size :
|
ovmi->stream_size :
|
||||||
get_streamfile_size(streamFile) - start;
|
get_streamfile_size(streamFile) - start;
|
||||||
|
int disable_reordering = ovmi->disable_reordering;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* proceed to init codec_data and reopen a STREAMFILE for this stream */
|
//todo improve how to pass config
|
||||||
{
|
io.decryption_callback = ovmi->decryption_callback;
|
||||||
char filename[PATH_LIMIT];
|
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));
|
data = init_ogg_vorbis(streamFile, start, stream_size, &io);
|
||||||
if (!data) goto fail;
|
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;
|
|
||||||
|
|
||||||
/* search for loop comments */
|
/* search for loop comments */
|
||||||
{//todo ignore if loop flag already set?
|
{//todo ignore if loop flag already set?
|
||||||
int i;
|
const char * comment = NULL;
|
||||||
vorbis_comment *comment = ov_comment(ovf,OGG_DEFAULT_BITSTREAM);
|
|
||||||
|
|
||||||
for (i = 0; i < comment->comments; i++) {
|
while (ogg_vorbis_get_comment(data, &comment)) {
|
||||||
const char * user_comment = comment->user_comments[i];
|
|
||||||
if (strstr(user_comment,"loop_start=")==user_comment || /* PSO4 */
|
if (strstr(comment,"loop_start=") == comment || /* PSO4 */
|
||||||
strstr(user_comment,"LOOP_START=")==user_comment || /* PSO4 */
|
strstr(comment,"LOOP_START=") == comment || /* PSO4 */
|
||||||
strstr(user_comment,"COMMENT=LOOPPOINT=")==user_comment ||
|
strstr(comment,"COMMENT=LOOPPOINT=") == comment ||
|
||||||
strstr(user_comment,"LOOPSTART=")==user_comment ||
|
strstr(comment,"LOOPSTART=") == comment ||
|
||||||
strstr(user_comment,"um3.stream.looppoint.start=")==user_comment ||
|
strstr(comment,"um3.stream.looppoint.start=") == comment ||
|
||||||
strstr(user_comment,"LOOP_BEGIN=")==user_comment || /* Hatsune Miku: Project Diva F (PS3) */
|
strstr(comment,"LOOP_BEGIN=") == comment || /* Hatsune Miku: Project Diva F (PS3) */
|
||||||
strstr(user_comment,"LoopStart=")==user_comment || /* Devil May Cry 4 (PC) */
|
strstr(comment,"LoopStart=") == comment || /* Devil May Cry 4 (PC) */
|
||||||
strstr(user_comment,"XIPH_CUE_LOOPSTART=")==user_comment) { /* Super Mario Run (Android) */
|
strstr(comment,"XIPH_CUE_LOOPSTART=") == comment) { /* Super Mario Run (Android) */
|
||||||
loop_start = atol(strrchr(user_comment,'=')+1);
|
loop_start = atol(strrchr(comment,'=')+1);
|
||||||
loop_flag = (loop_start >= 0);
|
loop_flag = (loop_start >= 0);
|
||||||
}
|
}
|
||||||
else if (strstr(user_comment,"LOOPLENGTH=")==user_comment) {/* (LOOPSTART pair) */
|
else if (strstr(comment,"LOOPLENGTH=") == comment) {/* (LOOPSTART pair) */
|
||||||
loop_length = atol(strrchr(user_comment,'=')+1);
|
loop_length = atol(strrchr(comment,'=')+1);
|
||||||
loop_length_found = 1;
|
loop_length_found = 1;
|
||||||
}
|
}
|
||||||
else if (strstr(user_comment,"title=-lps")==user_comment) { /* KID [Memories Off #5 (PC), Remember11 (PC)] */
|
else if (strstr(comment,"title=-lps") == comment) { /* KID [Memories Off #5 (PC), Remember11 (PC)] */
|
||||||
loop_start = atol(user_comment+10);
|
loop_start = atol(comment+10);
|
||||||
loop_flag = (loop_start >= 0);
|
loop_flag = (loop_start >= 0);
|
||||||
}
|
}
|
||||||
else if (strstr(user_comment,"album=-lpe")==user_comment) { /* (title=-lps pair) */
|
else if (strstr(comment,"album=-lpe") == comment) { /* (title=-lps pair) */
|
||||||
loop_end = atol(user_comment+10);
|
loop_end = atol(comment+10);
|
||||||
loop_flag = 1;
|
loop_flag = 1;
|
||||||
loop_end_found = 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) {
|
if(loop_flag) {
|
||||||
loop_length = atol(strrchr(user_comment,'=')+1)-loop_start;
|
loop_length = atol(strrchr(comment,'=')+1)-loop_start;
|
||||||
loop_length_found = 1;
|
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) {
|
if(loop_flag) {
|
||||||
loop_length = atol(strrchr(user_comment,'=')+1)-loop_start;
|
loop_length = atol(strrchr(comment,'=')+1)-loop_start;
|
||||||
loop_length_found = 1;
|
loop_length_found = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (strstr(user_comment,"lp=")==user_comment) {
|
else if (strstr(comment,"lp=") == comment) {
|
||||||
sscanf(strrchr(user_comment,'=')+1,"%d,%d", &loop_start,&loop_end);
|
sscanf(strrchr(comment,'=')+1,"%d,%d", &loop_start,&loop_end);
|
||||||
loop_flag = 1;
|
loop_flag = 1;
|
||||||
loop_end_found = 1;
|
loop_end_found = 1;
|
||||||
}
|
}
|
||||||
else if (strstr(user_comment,"LOOPDEFS=")==user_comment) { /* Fairy Fencer F: Advent Dark Force */
|
else if (strstr(comment,"LOOPDEFS=") == comment) { /* Fairy Fencer F: Advent Dark Force */
|
||||||
sscanf(strrchr(user_comment,'=')+1,"%d,%d", &loop_start,&loop_end);
|
sscanf(strrchr(comment,'=')+1,"%d,%d", &loop_start,&loop_end);
|
||||||
loop_flag = 1;
|
loop_flag = 1;
|
||||||
loop_end_found = 1;
|
loop_end_found = 1;
|
||||||
}
|
}
|
||||||
else if (strstr(user_comment,"COMMENT=loop(")==user_comment) { /* Zero Time Dilemma (PC) */
|
else if (strstr(comment,"COMMENT=loop(") == comment) { /* Zero Time Dilemma (PC) */
|
||||||
sscanf(strrchr(user_comment,'(')+1,"%d,%d", &loop_start,&loop_end);
|
sscanf(strrchr(comment,'(')+1,"%d,%d", &loop_start,&loop_end);
|
||||||
loop_flag = 1;
|
loop_flag = 1;
|
||||||
loop_end_found = 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) {
|
if (loop_flag) {
|
||||||
loop_length = atol(strrchr(user_comment, '=') + 1) - loop_start;
|
loop_length = atol(strrchr(comment, '=') + 1) - loop_start;
|
||||||
loop_length_found = 1;
|
loop_length_found = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (strstr(user_comment, "omment=") == user_comment) { /* Air (Android) */
|
else if (strstr(comment, "omment=") == comment) { /* Air (Android) */
|
||||||
sscanf(strstr(user_comment, "=LOOPSTART=") + 11, "%d,LOOPEND=%d", &loop_start, &loop_end);
|
sscanf(strstr(comment, "=LOOPSTART=") + 11, "%d,LOOPEND=%d", &loop_start, &loop_end);
|
||||||
loop_flag = 1;
|
loop_flag = 1;
|
||||||
loop_end_found = 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)" */
|
/* uses LoopStart=-1 LoopEnd=-1, then 3 secuential comments: "MarkerNum" + "M=7F(start)" + "M=7F(end)" */
|
||||||
loop_flag = 1;
|
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 */
|
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) {
|
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;
|
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
|
/* Hatsune Miku Project DIVA games, though only 'Arcade Future Tone' has >4ch files
|
||||||
* ENCODER tag is common but ogg_vorbis_encode looks unique enough
|
* ENCODER tag is common but ogg_vorbis_encode looks unique enough
|
||||||
* (arcade ends with "2010-11-26" while consoles have "2011-02-07" */
|
* (arcade ends with "2010-11-26" while consoles have "2011-02-07" */
|
||||||
if (strstr(user_comment, "ENCODER=ogg_vorbis_encode/") == user_comment) {
|
if (strstr(comment, "ENCODER=ogg_vorbis_encode/") == comment) {
|
||||||
data->disable_reordering = 1;
|
disable_reordering = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strstr(user_comment, "TITLE=") == user_comment) {
|
if (strstr(comment, "TITLE=") == comment) {
|
||||||
strncpy(name, user_comment + 6, sizeof(name) - 1);
|
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 */
|
/* build the VGMSTREAM */
|
||||||
vgmstream = allocate_vgmstream(vi->channels,loop_flag);
|
vgmstream = allocate_vgmstream(channels,loop_flag);
|
||||||
if (!vgmstream) goto fail;
|
if (!vgmstream) goto fail;
|
||||||
|
|
||||||
vgmstream->codec_data = data; /* store our fun extra datas */
|
vgmstream->codec_data = data;
|
||||||
vgmstream->channels = vi->channels;
|
vgmstream->coding_type = coding_OGG_VORBIS;
|
||||||
vgmstream->sample_rate = vi->rate;
|
vgmstream->layout_type = layout_none;
|
||||||
|
vgmstream->meta_type = ovmi->meta_type;
|
||||||
|
|
||||||
|
vgmstream->sample_rate = sample_rate;
|
||||||
vgmstream->stream_size = stream_size;
|
vgmstream->stream_size = stream_size;
|
||||||
|
|
||||||
if (ovmi->total_subsongs) /* not setting it has some effect when showing stream names */
|
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')
|
if (name[0] != '\0')
|
||||||
strcpy(vgmstream->stream_name, name);
|
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) {
|
if (loop_flag) {
|
||||||
vgmstream->loop_start_sample = loop_start;
|
vgmstream->loop_start_sample = loop_start;
|
||||||
if (loop_length_found)
|
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)
|
else if (loop_end_found)
|
||||||
vgmstream->loop_end_sample = loop_end;
|
vgmstream->loop_end_sample = loop_end;
|
||||||
else
|
else
|
||||||
@ -679,10 +565,6 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb
|
|||||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
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) {
|
if (vgmstream->channels <= 8) {
|
||||||
vgmstream->channel_layout = xiph_mappings[vgmstream->channels];
|
vgmstream->channel_layout = xiph_mappings[vgmstream->channels];
|
||||||
}
|
}
|
||||||
@ -690,18 +572,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb
|
|||||||
return vgmstream;
|
return vgmstream;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
/* clean up anything we may have opened */
|
close_vgmstream(vgmstream);
|
||||||
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);
|
|
||||||
}
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,23 +416,24 @@ fail:
|
|||||||
|
|
||||||
#ifdef VGM_USE_VORBIS
|
#ifdef VGM_USE_VORBIS
|
||||||
static void scd_ogg_v2_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
static void scd_ogg_v2_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
||||||
size_t bytes_read = size*nmemb;
|
uint8_t *ptr8 = ptr;
|
||||||
ogg_vorbis_streamfile * ov_streamfile = (ogg_vorbis_streamfile*)datasource;
|
size_t bytes_read = size * nmemb;
|
||||||
|
ogg_vorbis_io *io = datasource;
|
||||||
|
|
||||||
/* no encryption, sometimes happens */
|
/* no encryption, sometimes happens */
|
||||||
if (ov_streamfile->scd_xor == 0x00)
|
if (io->scd_xor == 0x00)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* header is XOR'd with a constant byte */
|
/* 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;
|
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)
|
if (num_crypt > bytes_read)
|
||||||
num_crypt = bytes_read;
|
num_crypt = bytes_read;
|
||||||
|
|
||||||
for (i = 0; i < num_crypt; i++) {
|
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
|
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
|
0x83, 0x26, 0xF9, 0x83, 0x2E, 0xFF, 0xE3, 0x16, 0x7D, 0xC0, 0x1E, 0x63, 0x21, 0x07, 0xE3, 0x01, // F0-FF
|
||||||
};
|
};
|
||||||
|
uint8_t *ptr8 = ptr;
|
||||||
size_t bytes_read = size*nmemb;
|
size_t bytes_read = size * nmemb;
|
||||||
ogg_vorbis_streamfile *ov_streamfile = (ogg_vorbis_streamfile*)datasource;
|
ogg_vorbis_io *io = datasource;
|
||||||
|
|
||||||
/* file is XOR'd with a table (algorithm and table by Ioncannon) */
|
/* 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;
|
int i, num_crypt;
|
||||||
uint8_t byte1, byte2, xor_byte;
|
uint8_t byte1, byte2, xor_byte;
|
||||||
|
|
||||||
num_crypt = bytes_read;
|
num_crypt = bytes_read;
|
||||||
byte1 = ov_streamfile->scd_xor & 0x7F;
|
byte1 = io->scd_xor & 0x7F;
|
||||||
byte2 = ov_streamfile->scd_xor & 0x3F;
|
byte2 = io->scd_xor & 0x3F;
|
||||||
|
|
||||||
for (i = 0; i < num_crypt; i++) {
|
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 &= 0xFF;
|
||||||
xor_byte ^= ((uint8_t*)ptr)[i];
|
xor_byte ^= ptr8[i];
|
||||||
xor_byte ^= byte1;
|
xor_byte ^= byte1;
|
||||||
((uint8_t*)ptr)[i] = (uint8_t)xor_byte;
|
ptr8[i] = xor_byte;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2628,8 +2628,7 @@ static STREAMFILE * get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM *
|
|||||||
|
|
||||||
#ifdef VGM_USE_VORBIS
|
#ifdef VGM_USE_VORBIS
|
||||||
if (vgmstream->coding_type == coding_OGG_VORBIS) {
|
if (vgmstream->coding_type == coding_OGG_VORBIS) {
|
||||||
ogg_vorbis_codec_data *data = vgmstream->codec_data;
|
return ogg_vorbis_get_streamfile(vgmstream->codec_data);
|
||||||
return data ? data->ov_streamfile.streamfile : NULL;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (vgmstream->coding_type == coding_CRI_HCA) {
|
if (vgmstream->coding_type == coding_CRI_HCA) {
|
||||||
|
@ -897,7 +897,8 @@ typedef struct {
|
|||||||
} VGMSTREAM;
|
} VGMSTREAM;
|
||||||
|
|
||||||
#ifdef VGM_USE_VORBIS
|
#ifdef VGM_USE_VORBIS
|
||||||
/* Ogg with Vorbis */
|
|
||||||
|
/* standard Ogg Vorbis */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
STREAMFILE *streamfile;
|
STREAMFILE *streamfile;
|
||||||
ogg_int64_t start; /* file offset where the Ogg starts */
|
ogg_int64_t start; /* file offset where the Ogg starts */
|
||||||
@ -910,15 +911,9 @@ typedef struct {
|
|||||||
off_t scd_xor_length;
|
off_t scd_xor_length;
|
||||||
uint32_t xor_value;
|
uint32_t xor_value;
|
||||||
|
|
||||||
} ogg_vorbis_streamfile;
|
} ogg_vorbis_io;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct ogg_vorbis_codec_data ogg_vorbis_codec_data;
|
||||||
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;
|
|
||||||
|
|
||||||
|
|
||||||
/* custom Vorbis modes */
|
/* custom Vorbis modes */
|
||||||
|
Loading…
Reference in New Issue
Block a user