2016-11-12 00:39:40 +01:00
|
|
|
#include "../vgmstream.h"
|
|
|
|
|
|
|
|
#ifdef VGM_USE_VORBIS
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "meta.h"
|
|
|
|
#include <vorbis/vorbisfile.h>
|
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
#define OGG_DEFAULT_BITSTREAM 0
|
2016-11-12 00:39:40 +01:00
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void * datasource) {
|
2016-11-12 00:39:40 +01:00
|
|
|
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
2018-01-10 21:12:23 +01:00
|
|
|
size_t bytes_read, items_read;
|
2016-11-12 00:39:40 +01:00
|
|
|
|
2018-01-11 22:55:23 +01:00
|
|
|
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);
|
2016-11-12 00:39:40 +01:00
|
|
|
items_read = bytes_read / size;
|
|
|
|
|
2017-01-03 19:03:08 +01:00
|
|
|
/* may be encrypted */
|
2018-01-10 21:12:23 +01:00
|
|
|
if (ov_streamfile->decryption_callback) {
|
|
|
|
ov_streamfile->decryption_callback(ptr, size, items_read, ov_streamfile);
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ov_streamfile->offset += items_read * size;
|
|
|
|
|
|
|
|
return items_read;
|
|
|
|
}
|
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) {
|
2016-11-12 00:39:40 +01:00
|
|
|
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
2018-01-10 21:12:23 +01:00
|
|
|
ogg_int64_t base_offset, new_offset;
|
2016-11-12 00:39:40 +01:00
|
|
|
|
|
|
|
switch (whence) {
|
|
|
|
case SEEK_SET:
|
|
|
|
base_offset = 0;
|
|
|
|
break;
|
|
|
|
case SEEK_CUR:
|
|
|
|
base_offset = ov_streamfile->offset;
|
|
|
|
break;
|
|
|
|
case SEEK_END:
|
2018-01-11 22:55:23 +01:00
|
|
|
base_offset = ov_streamfile->size;
|
2016-11-12 00:39:40 +01:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
|
2016-11-12 00:39:40 +01:00
|
|
|
new_offset = base_offset + offset;
|
2018-01-11 22:55:23 +01:00
|
|
|
if (new_offset < 0 || new_offset > ov_streamfile->size) {
|
2018-01-10 21:12:23 +01:00
|
|
|
return -1; /* *must* return -1 if stream is unseekable */
|
2016-11-12 00:39:40 +01:00
|
|
|
} else {
|
|
|
|
ov_streamfile->offset = new_offset;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
static long ov_tell_func(void * datasource) {
|
2016-11-12 00:39:40 +01:00
|
|
|
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
|
|
|
return ov_streamfile->offset;
|
|
|
|
}
|
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
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) */
|
2016-11-12 00:39:40 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
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;
|
|
|
|
int i;
|
2016-11-12 00:39:40 +01:00
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
/* first 0x800 bytes are xor'd with 0xff */
|
|
|
|
if (ov_streamfile->offset < 0x800) {
|
|
|
|
int num_crypt = 0x800 - ov_streamfile->offset;
|
|
|
|
if (num_crypt > bytes_read)
|
|
|
|
num_crypt = bytes_read;
|
2016-11-12 00:39:40 +01:00
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
for (i = 0; i < num_crypt; i++)
|
|
|
|
((uint8_t*)ptr)[i] ^= 0xff;
|
|
|
|
}
|
|
|
|
}
|
2016-11-12 00:39:40 +01:00
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
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;
|
|
|
|
int i;
|
2016-11-12 00:39:40 +01:00
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
/* first 0x100 bytes are xor'd with offset */
|
|
|
|
if (ov_streamfile->offset < 0x100) {
|
|
|
|
int max_offset = ov_streamfile->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;
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
|
|
|
}
|
2018-01-10 21:12:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void psychic_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
|
|
|
size_t bytes_read = size*nmemb;
|
|
|
|
int i;
|
2016-11-12 00:39:40 +01:00
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
/* add 0x23 ('#') */
|
|
|
|
{
|
|
|
|
for (i = 0; i < bytes_read; i++)
|
|
|
|
((uint8_t*)ptr)[i] += 0x23;
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
2018-01-10 21:12:23 +01:00
|
|
|
}
|
2016-11-12 00:39:40 +01:00
|
|
|
|
2018-01-10 22:34:14 +01:00
|
|
|
static void sngw_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;
|
|
|
|
int i;
|
|
|
|
char *header_id = "OggS";
|
|
|
|
uint8_t key[4];
|
|
|
|
|
|
|
|
put_32bitBE(key, ov_streamfile->sngw_xor);
|
|
|
|
|
|
|
|
/* bytes are xor'd with key and nibble-swapped */
|
|
|
|
{
|
|
|
|
for (i = 0; i < bytes_read; i++) {
|
|
|
|
if (ov_streamfile->offset+i < 0x04) {
|
|
|
|
/* replace key in the first 4 bytes with "OggS" */
|
|
|
|
((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
uint8_t val = ((uint8_t*)ptr)[i] ^ key[(ov_streamfile->offset + i) % 4];
|
|
|
|
((uint8_t*)ptr)[i] = ((val << 4) & 0xf0) | ((val >> 4) & 0x0f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-13 15:22:58 +01:00
|
|
|
static void isd_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
|
|
|
static const uint8_t key[16] = {
|
|
|
|
0xe0,0x00,0xe0,0x00,0xa0,0x00,0x00,0x00,0xe0,0x00,0xe0,0x80,0x40,0x40,0x40,0x00
|
|
|
|
};
|
|
|
|
size_t bytes_read = size*nmemb;
|
|
|
|
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* bytes are xor'd with key */
|
|
|
|
{
|
|
|
|
for (i = 0; i < bytes_read; i++)
|
|
|
|
((uint8_t*)ptr)[i] ^= key[(ov_streamfile->offset + i) % 16];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-17 02:23:45 +01:00
|
|
|
static void l2sd_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;
|
|
|
|
int i;
|
|
|
|
char *header_id = "OggS";
|
|
|
|
|
|
|
|
/* First "OggS" is changed */
|
|
|
|
{
|
|
|
|
for (i = 0; i < bytes_read; i++) {
|
|
|
|
if (ov_streamfile->offset+i < 0x04) {
|
|
|
|
/* replace key in the first 4 bytes with "OggS" */
|
|
|
|
((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
|
|
|
|
/* Ogg Vorbis, by way of libvorbisfile; may contain loop comments */
|
|
|
|
VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
|
|
|
char filename[PATH_LIMIT];
|
|
|
|
vgm_vorbis_info_t inf = {0};
|
|
|
|
off_t start_offset = 0;
|
|
|
|
|
|
|
|
int is_ogg = 0;
|
|
|
|
int is_um3 = 0;
|
|
|
|
int is_kovs = 0;
|
|
|
|
int is_psychic = 0;
|
2018-01-10 22:34:14 +01:00
|
|
|
int is_sngw = 0;
|
2018-01-13 15:22:58 +01:00
|
|
|
int is_isd = 0;
|
2018-02-17 02:23:45 +01:00
|
|
|
int is_l2sd = 0;
|
2018-01-10 21:12:23 +01:00
|
|
|
|
|
|
|
|
|
|
|
/* check extension */
|
2018-03-03 01:10:39 +01:00
|
|
|
if (check_extensions(streamFile,"ogg,logg,adx")) { /* .ogg: standard/psychic, .logg: renamed for plugins, .adx: KID [Remember11 (PC)] */
|
2018-01-10 21:12:23 +01:00
|
|
|
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? */
|
|
|
|
is_kovs = 1;
|
2018-03-03 01:10:39 +01:00
|
|
|
} else if (check_extensions(streamFile,"sngw")) { /* .sngw: Capcom [Devil May Cry 4 SE (PC), Biohazard 6 (PC)] */
|
2018-01-10 22:34:14 +01:00
|
|
|
is_sngw = 1;
|
2018-01-13 15:22:58 +01:00
|
|
|
} else if (check_extensions(streamFile,"isd")) { /* .isd: Azure Striker Gunvolt (PC) */
|
|
|
|
is_isd = 1;
|
2018-01-10 21:12:23 +01:00
|
|
|
} else {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
streamFile->get_name(streamFile,filename,sizeof(filename));
|
|
|
|
|
|
|
|
/* check standard Ogg Vorbis */
|
|
|
|
if (is_ogg) {
|
|
|
|
|
|
|
|
/* check Psychic Software obfuscation (Darkwind: War on Wheels PC) */
|
|
|
|
if (read_32bitBE(0x00,streamFile) == 0x2c444430) {
|
|
|
|
is_psychic = 1;
|
|
|
|
inf.decryption_callback = psychic_ogg_decryption_callback;
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
2018-02-17 02:23:45 +01:00
|
|
|
else if (read_32bitBE(0x00,streamFile) == 0x4C325344) { /* "L2SD" [Lineage II Chronicle 4 (PC)] */
|
|
|
|
is_l2sd = 1;
|
|
|
|
inf.decryption_callback = l2sd_ogg_decryption_callback;
|
|
|
|
}
|
2018-01-10 21:12:23 +01:00
|
|
|
else if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" */
|
|
|
|
goto fail; /* not known (ex. Wwise) */
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
/* check "Ultramarine3" (???), may be encrypted */
|
|
|
|
if (is_um3) {
|
|
|
|
if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" */
|
|
|
|
inf.decryption_callback = um3_ogg_decryption_callback;
|
|
|
|
}
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
/* check KOVS (Koei Tecmo games), encrypted and has an actual header */
|
|
|
|
if (is_kovs) {
|
|
|
|
if (read_32bitBE(0x00,streamFile) != 0x4b4f5653) { /* "KOVS" */
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
inf.loop_start = read_32bitLE(0x08,streamFile);
|
|
|
|
inf.loop_flag = (inf.loop_start != 0);
|
|
|
|
inf.decryption_callback = kovs_ogg_decryption_callback;
|
|
|
|
|
|
|
|
start_offset = 0x20;
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
|
|
|
|
2018-01-10 22:34:14 +01:00
|
|
|
/* check SNGW (Capcom's MT Framework PC games), may be encrypted */
|
|
|
|
if (is_sngw) {
|
|
|
|
if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" */
|
|
|
|
inf.sngw_xor = read_32bitBE(0x00,streamFile);
|
|
|
|
inf.decryption_callback = sngw_ogg_decryption_callback;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-13 15:22:58 +01:00
|
|
|
/* check ISD (Gunvolt PC) */
|
|
|
|
if (is_isd) {
|
|
|
|
inf.decryption_callback = isd_ogg_decryption_callback;
|
|
|
|
|
|
|
|
//todo looping unknown, not in Ogg comments
|
|
|
|
// game has sound/GV_steam.* files with info about sound/stream/*.isd
|
|
|
|
//- .ish: constant id/names
|
|
|
|
//- .isl: unknown table, maybe looping?
|
|
|
|
//- .isf: format table, ordered like file numbers, 0x18 header with:
|
|
|
|
// 0x00(2): ?, 0x02(2): channels, 0x04: sample rate, 0x08: skip samples (in PCM bytes), always 32000
|
|
|
|
// 0x0c(2): PCM block size, 0x0e(2): PCM bps, 0x10: null, 0x18: samples (in PCM bytes)
|
|
|
|
}
|
|
|
|
|
2018-01-10 22:34:14 +01:00
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
if (is_um3) {
|
2017-01-08 01:09:20 +01:00
|
|
|
inf.meta_type = meta_OGG_UM3;
|
2018-01-10 21:12:23 +01:00
|
|
|
} else if (is_kovs) {
|
2017-01-08 01:09:20 +01:00
|
|
|
inf.meta_type = meta_OGG_KOVS;
|
2018-01-10 21:12:23 +01:00
|
|
|
} else if (is_psychic) {
|
2018-01-13 15:22:58 +01:00
|
|
|
inf.meta_type = meta_OGG_PSYCHIC;
|
2018-01-10 22:34:14 +01:00
|
|
|
} else if (is_sngw) {
|
|
|
|
inf.meta_type = meta_OGG_SNGW;
|
2018-01-13 15:22:58 +01:00
|
|
|
} else if (is_isd) {
|
|
|
|
inf.meta_type = meta_OGG_ISD;
|
2018-02-17 02:23:45 +01:00
|
|
|
} else if (is_l2sd) {
|
|
|
|
inf.meta_type = meta_OGG_L2SD;
|
2016-11-12 00:39:40 +01:00
|
|
|
} else {
|
2017-01-08 01:09:20 +01:00
|
|
|
inf.meta_type = meta_OGG_VORBIS;
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
|
|
|
inf.layout_type = layout_ogg_vorbis;
|
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);
|
2016-11-12 00:39:40 +01:00
|
|
|
|
|
|
|
fail:
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-01-11 22:55:23 +01:00
|
|
|
VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const char * filename, ov_callbacks *callbacks_p, off_t start, const vgm_vorbis_info_t *vgm_inf) {
|
2016-11-12 00:39:40 +01:00
|
|
|
VGMSTREAM * vgmstream = NULL;
|
|
|
|
ogg_vorbis_codec_data * data = NULL;
|
2018-01-10 21:12:23 +01:00
|
|
|
OggVorbis_File *ovf = NULL;
|
|
|
|
vorbis_info *vi;
|
2016-11-12 00:39:40 +01:00
|
|
|
|
|
|
|
int loop_flag = vgm_inf->loop_flag;
|
|
|
|
int32_t loop_start = vgm_inf->loop_start;
|
|
|
|
int loop_length_found = vgm_inf->loop_length_found;
|
|
|
|
int32_t loop_length = vgm_inf->loop_length;
|
|
|
|
int loop_end_found = vgm_inf->loop_end_found;
|
|
|
|
int32_t loop_end = vgm_inf->loop_end;
|
2018-01-28 00:41:25 +01:00
|
|
|
size_t stream_size = vgm_inf->stream_size ?
|
|
|
|
vgm_inf->stream_size :
|
|
|
|
get_streamfile_size(streamFile) - start;
|
2016-11-12 00:39:40 +01:00
|
|
|
|
|
|
|
ov_callbacks default_callbacks;
|
|
|
|
|
|
|
|
if (!callbacks_p) {
|
2018-01-10 21:12:23 +01:00
|
|
|
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;
|
2016-11-12 00:39:40 +01:00
|
|
|
|
|
|
|
callbacks_p = &default_callbacks;
|
|
|
|
}
|
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
/* test if this is a proper Ogg Vorbis file, with the current (from init_x) STREAMFILE */
|
|
|
|
{
|
|
|
|
OggVorbis_File temp_ovf;
|
|
|
|
ogg_vorbis_streamfile temp_streamfile;
|
|
|
|
|
|
|
|
temp_streamfile.streamfile = streamFile;
|
2018-01-11 22:55:23 +01:00
|
|
|
|
|
|
|
temp_streamfile.start = start;
|
2018-01-10 21:12:23 +01:00
|
|
|
temp_streamfile.offset = 0;
|
2018-01-28 00:41:25 +01:00
|
|
|
temp_streamfile.size = stream_size;
|
2018-01-11 22:55:23 +01:00
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
temp_streamfile.decryption_callback = vgm_inf->decryption_callback;
|
|
|
|
temp_streamfile.scd_xor = vgm_inf->scd_xor;
|
|
|
|
temp_streamfile.scd_xor_length = vgm_inf->scd_xor_length;
|
2018-01-10 22:34:14 +01:00
|
|
|
temp_streamfile.sngw_xor = vgm_inf->sngw_xor;
|
2018-01-10 21:12:23 +01:00
|
|
|
|
|
|
|
/* open the ogg vorbis file for testing */
|
|
|
|
memset(&temp_ovf, 0, sizeof(temp_ovf));
|
|
|
|
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 */
|
|
|
|
{
|
|
|
|
data = calloc(1,sizeof(ogg_vorbis_codec_data));
|
|
|
|
if (!data) goto fail;
|
|
|
|
|
|
|
|
data->ov_streamfile.streamfile = streamFile->open(streamFile,filename, STREAMFILE_DEFAULT_BUFFER_SIZE);
|
|
|
|
if (!data->ov_streamfile.streamfile) goto fail;
|
|
|
|
|
2018-01-11 22:55:23 +01:00
|
|
|
data->ov_streamfile.start = start;
|
2018-01-10 21:12:23 +01:00
|
|
|
data->ov_streamfile.offset = 0;
|
2018-01-28 00:41:25 +01:00
|
|
|
data->ov_streamfile.size = stream_size;
|
2018-01-11 22:55:23 +01:00
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
data->ov_streamfile.decryption_callback = vgm_inf->decryption_callback;
|
|
|
|
data->ov_streamfile.scd_xor = vgm_inf->scd_xor;
|
|
|
|
data->ov_streamfile.scd_xor_length = vgm_inf->scd_xor_length;
|
2018-01-10 22:34:14 +01:00
|
|
|
data->ov_streamfile.sngw_xor = vgm_inf->sngw_xor;
|
2018-01-10 21:12:23 +01:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get info from bitstream 0 */
|
|
|
|
data->bitstream = OGG_DEFAULT_BITSTREAM;
|
|
|
|
vi = ov_info(ovf,OGG_DEFAULT_BITSTREAM);
|
|
|
|
|
|
|
|
/* search for loop comments */
|
2016-11-12 00:39:40 +01:00
|
|
|
{
|
|
|
|
int i;
|
2018-01-10 21:12:23 +01:00
|
|
|
vorbis_comment *comment = ov_comment(ovf,OGG_DEFAULT_BITSTREAM);
|
|
|
|
|
|
|
|
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) */
|
|
|
|
loop_start = atol(strrchr(user_comment,'=')+1);
|
|
|
|
loop_flag = (loop_start >= 0);
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
2018-01-10 21:12:23 +01:00
|
|
|
else if (strstr(user_comment,"LOOPLENGTH=")==user_comment) {/* (LOOPSTART pair) */
|
|
|
|
loop_length = atol(strrchr(user_comment,'=')+1);
|
|
|
|
loop_length_found = 1;
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
2018-03-03 01:10:39 +01:00
|
|
|
else if (strstr(user_comment,"title=-lps")==user_comment) { /* KID [Memories Off #5 (PC), Remember11 (PC)] */
|
2018-01-10 21:12:23 +01:00
|
|
|
loop_start = atol(user_comment+10);
|
|
|
|
loop_flag = (loop_start >= 0);
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
2018-01-10 21:12:23 +01:00
|
|
|
else if (strstr(user_comment,"album=-lpe")==user_comment) { /* (title=-lps pair) */
|
|
|
|
loop_end = atol(user_comment+10);
|
|
|
|
loop_flag = 1;
|
|
|
|
loop_end_found = 1;
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
2018-01-10 21:12:23 +01:00
|
|
|
else if (strstr(user_comment,"LoopEnd=")==user_comment) { /* (LoopStart pair) */
|
|
|
|
if(loop_flag) {
|
|
|
|
loop_length = atol(strrchr(user_comment,'=')+1)-loop_start;
|
|
|
|
loop_length_found = 1;
|
|
|
|
}
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
2018-01-10 21:12:23 +01:00
|
|
|
else if (strstr(user_comment,"LOOP_END=")==user_comment) { /* (LOOP_BEGIN pair) */
|
|
|
|
if(loop_flag) {
|
|
|
|
loop_length = atol(strrchr(user_comment,'=')+1)-loop_start;
|
|
|
|
loop_length_found = 1;
|
|
|
|
}
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
2018-01-10 21:12:23 +01:00
|
|
|
else if (strstr(user_comment,"lp=")==user_comment) {
|
|
|
|
sscanf(strrchr(user_comment,'=')+1,"%d,%d", &loop_start,&loop_end);
|
|
|
|
loop_flag = 1;
|
|
|
|
loop_end_found = 1;
|
2017-02-18 08:04:16 +01:00
|
|
|
}
|
2018-01-10 21:12:23 +01:00
|
|
|
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);
|
|
|
|
loop_flag = 1;
|
|
|
|
loop_end_found = 1;
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
2018-01-10 21:12:23 +01:00
|
|
|
else if (strstr(user_comment,"COMMENT=loop(")==user_comment) { /* Zero Time Dilemma (PC) */
|
|
|
|
sscanf(strrchr(user_comment,'(')+1,"%d,%d", &loop_start,&loop_end);
|
|
|
|
loop_flag = 1;
|
|
|
|
loop_end_found = 1;
|
2016-11-12 00:39:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
|
2016-11-12 00:39:40 +01:00
|
|
|
/* build the VGMSTREAM */
|
2018-01-10 21:12:23 +01:00
|
|
|
vgmstream = allocate_vgmstream(vi->channels,loop_flag);
|
2016-11-12 00:39:40 +01:00
|
|
|
if (!vgmstream) goto fail;
|
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
vgmstream->codec_data = data; /* store our fun extra datas */
|
|
|
|
vgmstream->channels = vi->channels;
|
|
|
|
vgmstream->sample_rate = vi->rate;
|
2018-01-11 22:55:23 +01:00
|
|
|
vgmstream->num_streams = vgm_inf->total_subsongs;
|
2018-01-28 00:41:25 +01:00
|
|
|
vgmstream->stream_size = stream_size;
|
2016-11-12 00:39:40 +01:00
|
|
|
|
2018-01-10 21:12:23 +01:00
|
|
|
vgmstream->num_samples = ov_pcm_total(ovf,-1); /* let libvorbisfile find total samples */
|
2016-11-12 00:39:40 +01:00
|
|
|
if (loop_flag) {
|
|
|
|
vgmstream->loop_start_sample = loop_start;
|
|
|
|
if (loop_length_found)
|
|
|
|
vgmstream->loop_end_sample = loop_start+loop_length;
|
|
|
|
else if (loop_end_found)
|
|
|
|
vgmstream->loop_end_sample = loop_end;
|
|
|
|
else
|
|
|
|
vgmstream->loop_end_sample = vgmstream->num_samples;
|
|
|
|
vgmstream->loop_flag = loop_flag;
|
|
|
|
|
|
|
|
if (vgmstream->loop_end_sample > vgmstream->num_samples)
|
|
|
|
vgmstream->loop_end_sample = vgmstream->num_samples;
|
|
|
|
}
|
2018-01-10 21:12:23 +01:00
|
|
|
|
2016-11-12 00:39:40 +01:00
|
|
|
vgmstream->coding_type = coding_ogg_vorbis;
|
|
|
|
vgmstream->layout_type = vgm_inf->layout_type;
|
|
|
|
vgmstream->meta_type = vgm_inf->meta_type;
|
|
|
|
|
|
|
|
return vgmstream;
|
|
|
|
|
|
|
|
fail:
|
2018-01-10 21:12:23 +01:00
|
|
|
/* clean up anything we may have opened */
|
2016-11-12 00:39:40 +01:00
|
|
|
if (data) {
|
2018-01-10 21:12:23 +01:00
|
|
|
if (ovf)
|
|
|
|
ov_clear(&data->ogg_vorbis_file);//same as ovf
|
2016-11-12 00:39:40 +01:00
|
|
|
if (data->ov_streamfile.streamfile)
|
|
|
|
close_streamfile(data->ov_streamfile.streamfile);
|
|
|
|
free(data);
|
|
|
|
}
|
|
|
|
if (vgmstream) {
|
|
|
|
vgmstream->codec_data = NULL;
|
|
|
|
close_vgmstream(vgmstream);
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|