mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-29 19:37:30 +01:00
commit
841755eb8b
6
.gitignore
vendored
6
.gitignore
vendored
@ -2,6 +2,8 @@
|
||||
*.user
|
||||
*.suo
|
||||
*.vspscc
|
||||
*.o
|
||||
*.a
|
||||
/ext_libs/Debug
|
||||
/ext_libs/Release
|
||||
/ext_libs/*.exp
|
||||
@ -13,4 +15,6 @@
|
||||
/src/Debug
|
||||
/src/Release
|
||||
/test/Debug
|
||||
/test/Release
|
||||
/test/Release
|
||||
/test/*.exe
|
||||
/winamp/*.dll
|
||||
|
@ -464,7 +464,8 @@ bool input_vgmstream::g_is_our_path(const char * p_path,const char * p_extension
|
||||
if(!stricmp_utf8(p_extension,"sfs")) return 1;
|
||||
if(!stricmp_utf8(p_extension,"sfx")) return 1;
|
||||
if(!stricmp_utf8(p_extension,"sgb")) return 1;
|
||||
if(!stricmp_utf8(p_extension,"sgd")) return 1;
|
||||
if(!stricmp_utf8(p_extension,"sgd")) return 1;
|
||||
if(!stricmp_utf8(p_extension,"sgx")) return 1;
|
||||
if(!stricmp_utf8(p_extension,"sl3")) return 1;
|
||||
if(!stricmp_utf8(p_extension,"sli")) return 1;
|
||||
if(!stricmp_utf8(p_extension,"smp")) return 1;
|
||||
@ -797,6 +798,7 @@ DECLARE_MULTIPLE_FILE_TYPE("SFS Audio File (*.SFS)", sfs);
|
||||
DECLARE_MULTIPLE_FILE_TYPE("SFX Audio File (*.SFX)", sfx);
|
||||
DECLARE_MULTIPLE_FILE_TYPE("SGB Audio File (*.SGB)", sgb);
|
||||
DECLARE_MULTIPLE_FILE_TYPE("SGD Audio File (*.SGD)", sgd);
|
||||
DECLARE_MULTIPLE_FILE_TYPE("SGX Audio File (*.SGX)", sgx);
|
||||
DECLARE_MULTIPLE_FILE_TYPE("SL3 Audio File (*.SL3)", sl3);
|
||||
DECLARE_MULTIPLE_FILE_TYPE("SLI Audio File (*.SLI)", sli);
|
||||
DECLARE_MULTIPLE_FILE_TYPE("SMP Audio File (*.SMP)", smp);
|
||||
|
@ -6,7 +6,7 @@ cd /d "%~dp0"
|
||||
|
||||
for /f %%v in ('git describe --always --tag') do set version=%%v
|
||||
|
||||
set version=!version:^:=_!
|
||||
if not "%version%"=="" set version=!version:^:=_!
|
||||
|
||||
if not "%version%"=="" goto :gotversion
|
||||
|
||||
|
@ -43,7 +43,7 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) {
|
||||
if (xb3d_flag) {
|
||||
channel_count = read_32bitLE(0, streamFile);
|
||||
loop_flag = read_16bitLE(0x6e, streamFile);
|
||||
channel_header_spacing = 0x34;
|
||||
channel_header_spacing = 0x34;
|
||||
}
|
||||
else {
|
||||
/* get stream offset, check for CRI signature just before */
|
||||
@ -109,6 +109,16 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) {
|
||||
loop_end_sample = read_32bitBE(0x30,streamFile);
|
||||
//loop_end_offset = read_32bitBE(0x34,streamFile);
|
||||
}
|
||||
|
||||
/* AINF header can also start after the loop points
|
||||
* (may be inserted by CRI's tools but is rarely used) */
|
||||
/* ainf_magic = read_32bitBE(0x38,streamFile); */ /* 0x41494E46 */
|
||||
/* ainf_length = read_32bitBE(0x3c,streamFile); */
|
||||
/* ainf_str_id = read_string(0x40,streamFile); */ /* max size 0x10 */
|
||||
/* ainf_volume = read_16bitBE(0x50,streamFile); */ /* 0=base/max?, negative=reduce */
|
||||
/* ainf_pan_l = read_16bitBE(0x54,streamFile); */ /* 0=base, max +-128 */
|
||||
/* ainf_pan_r = read_16bitBE(0x56,streamFile); */
|
||||
|
||||
} else if (version_signature == 0x0500) { /* found in some SFD : Buggy Heat, appears to have no loop */
|
||||
header_type = meta_ADX_05;
|
||||
} else goto fail; /* not a known/supported version signature */
|
||||
@ -150,7 +160,7 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) {
|
||||
= read_32bitLE(0x34+i*channel_header_spacing, streamFile);
|
||||
if (!vgmstream->ch[i].streamfile) goto fail;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
vgmstream->num_samples = read_32bitBE(0xc,streamFile);
|
||||
|
@ -1,448 +1,455 @@
|
||||
#include "../vgmstream.h"
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "meta.h"
|
||||
#include "../util.h"
|
||||
#include <vorbis/vorbisfile.h>
|
||||
|
||||
|
||||
#define DEFAULT_BITSTREAM 0
|
||||
|
||||
static size_t read_func(void *ptr, size_t size, size_t nmemb, void * datasource)
|
||||
{
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
size_t items_read;
|
||||
|
||||
size_t bytes_read;
|
||||
|
||||
bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb,
|
||||
ov_streamfile->streamfile);
|
||||
|
||||
items_read = bytes_read / size;
|
||||
|
||||
ov_streamfile->offset += items_read * size;
|
||||
|
||||
return items_read;
|
||||
}
|
||||
|
||||
static size_t read_func_um3(void *ptr, size_t size, size_t nmemb, void * datasource)
|
||||
{
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
size_t items_read;
|
||||
|
||||
size_t bytes_read;
|
||||
|
||||
bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb,
|
||||
ov_streamfile->streamfile);
|
||||
|
||||
items_read = bytes_read / size;
|
||||
|
||||
/* first 0x800 bytes of um3 are xor'd with 0xff */
|
||||
if (ov_streamfile->offset < 0x800) {
|
||||
int num_crypt = 0x800-ov_streamfile->offset;
|
||||
int i;
|
||||
|
||||
if (num_crypt > bytes_read) num_crypt=bytes_read;
|
||||
for (i=0;i<num_crypt;i++)
|
||||
((uint8_t*)ptr)[i] ^= 0xff;
|
||||
}
|
||||
|
||||
ov_streamfile->offset += items_read * size;
|
||||
|
||||
return items_read;
|
||||
}
|
||||
|
||||
static size_t read_func_kovs(void *ptr, size_t size, size_t nmemb, void * datasource)
|
||||
{
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
size_t items_read;
|
||||
|
||||
size_t bytes_read;
|
||||
|
||||
bytes_read = read_streamfile(ptr, ov_streamfile->offset+ov_streamfile->other_header_bytes, size * nmemb,
|
||||
ov_streamfile->streamfile);
|
||||
|
||||
items_read = bytes_read / size;
|
||||
|
||||
/* first 0x100 bytes of KOVS are xor'd with offset */
|
||||
if (ov_streamfile->offset < 0x100) {
|
||||
int i;
|
||||
|
||||
for (i=ov_streamfile->offset;i<0x100;i++)
|
||||
((uint8_t*)ptr)[i-ov_streamfile->offset] ^= i;
|
||||
}
|
||||
|
||||
ov_streamfile->offset += items_read * size;
|
||||
|
||||
return items_read;
|
||||
}
|
||||
|
||||
static size_t read_func_scd(void *ptr, size_t size, size_t nmemb, void * datasource)
|
||||
{
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
size_t items_read;
|
||||
|
||||
size_t bytes_read;
|
||||
|
||||
bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb,
|
||||
ov_streamfile->streamfile);
|
||||
|
||||
items_read = bytes_read / size;
|
||||
|
||||
/* first bytes are xor'd with a constant byte */
|
||||
if (ov_streamfile->offset < ov_streamfile->scd_xor_len) {
|
||||
int num_crypt = ov_streamfile->scd_xor_len-ov_streamfile->offset;
|
||||
int i;
|
||||
|
||||
if (num_crypt > bytes_read) num_crypt=bytes_read;
|
||||
for (i=0;i<num_crypt;i++)
|
||||
((uint8_t*)ptr)[i] ^= ov_streamfile->scd_xor;
|
||||
}
|
||||
|
||||
ov_streamfile->offset += items_read * size;
|
||||
|
||||
return items_read;
|
||||
}
|
||||
|
||||
static size_t read_func_psych(void *ptr, size_t size, size_t nmemb, void * datasource)
|
||||
{
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
size_t items_read;
|
||||
|
||||
size_t bytes_read;
|
||||
size_t i;
|
||||
|
||||
bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb,
|
||||
ov_streamfile->streamfile);
|
||||
|
||||
/* add 0x23 ('#') */
|
||||
for (i=0;i<bytes_read;i++)
|
||||
((uint8_t*)ptr)[i] += 0x23;
|
||||
|
||||
items_read = bytes_read / size;
|
||||
|
||||
ov_streamfile->offset += items_read * size;
|
||||
|
||||
return items_read;
|
||||
}
|
||||
|
||||
static int seek_func(void *datasource, ogg_int64_t offset, int whence) {
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
ogg_int64_t base_offset;
|
||||
ogg_int64_t 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 - ov_streamfile->other_header_bytes;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
|
||||
new_offset = base_offset + offset;
|
||||
if (new_offset < 0 || new_offset > (ov_streamfile->size - ov_streamfile->other_header_bytes)) {
|
||||
return -1;
|
||||
} else {
|
||||
ov_streamfile->offset = new_offset;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static long tell_func(void * datasource) {
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
return ov_streamfile->offset;
|
||||
}
|
||||
|
||||
/* setting close_func in ov_callbacks to NULL doesn't seem to work */
|
||||
static int close_func(void * datasource) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Ogg Vorbis, by way of libvorbisfile */
|
||||
|
||||
VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
||||
char filename[PATH_LIMIT];
|
||||
|
||||
ov_callbacks callbacks;
|
||||
|
||||
off_t other_header_bytes = 0;
|
||||
int um3_ogg = 0;
|
||||
int kovs_ogg = 0;
|
||||
int psych_ogg = 0;
|
||||
|
||||
vgm_vorbis_info_t inf;
|
||||
memset(&inf, 0, sizeof(inf));
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
|
||||
/* It is only interesting to use oggs with vgmstream if they are looped.
|
||||
To prevent such files from being played by other plugins and such they
|
||||
may be renamed to .logg. This meta reader should still support .ogg,
|
||||
though. */
|
||||
if (strcasecmp("logg",filename_extension(filename)) &&
|
||||
strcasecmp("ogg",filename_extension(filename))) {
|
||||
if (!strcasecmp("um3",filename_extension(filename))) {
|
||||
um3_ogg = 1;
|
||||
} else if (!strcasecmp("kovs",filename_extension(filename))) {
|
||||
kovs_ogg = 1;
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* not all um3-ogg are crypted */
|
||||
if (um3_ogg && read_32bitBE(0x0,streamFile)==0x4f676753) {
|
||||
um3_ogg = 0;
|
||||
}
|
||||
|
||||
/* use KOVS header */
|
||||
if (kovs_ogg) {
|
||||
if (read_32bitBE(0x0,streamFile)!=0x4b4f5653) { /* "KOVS" */
|
||||
goto fail;
|
||||
}
|
||||
if (read_32bitLE(0x8,streamFile)!=0) {
|
||||
inf.loop_start = read_32bitLE(0x8,streamFile);
|
||||
inf.loop_flag = 1;
|
||||
}
|
||||
|
||||
other_header_bytes = 0x20;
|
||||
}
|
||||
|
||||
/* detect Psychic Software obfuscation (as seen in "Darkwind") */
|
||||
if (read_32bitBE(0x0,streamFile)==0x2c444430) {
|
||||
psych_ogg = 1;
|
||||
}
|
||||
|
||||
if (um3_ogg) {
|
||||
callbacks.read_func = read_func_um3;
|
||||
} else if (kovs_ogg) {
|
||||
callbacks.read_func = read_func_kovs;
|
||||
} else if (psych_ogg) {
|
||||
callbacks.read_func = read_func_psych;
|
||||
} else {
|
||||
callbacks.read_func = read_func;
|
||||
}
|
||||
callbacks.seek_func = seek_func;
|
||||
callbacks.close_func = close_func;
|
||||
callbacks.tell_func = tell_func;
|
||||
|
||||
if (um3_ogg) {
|
||||
inf.meta_type = meta_um3_ogg;
|
||||
} else if (kovs_ogg) {
|
||||
inf.meta_type = meta_KOVS_ogg;
|
||||
} else if (psych_ogg) {
|
||||
inf.meta_type = meta_psych_ogg;
|
||||
} else {
|
||||
inf.meta_type = meta_ogg_vorbis;
|
||||
}
|
||||
|
||||
inf.layout_type = layout_ogg_vorbis;
|
||||
|
||||
return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, &callbacks, other_header_bytes, &inf);
|
||||
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const char * filename, ov_callbacks *callbacks_p, off_t other_header_bytes, const vgm_vorbis_info_t *vgm_inf) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
|
||||
OggVorbis_File temp_ovf;
|
||||
ogg_vorbis_streamfile temp_streamfile;
|
||||
|
||||
ogg_vorbis_codec_data * data = NULL;
|
||||
OggVorbis_File *ovf;
|
||||
int inited_ovf = 0;
|
||||
vorbis_info *info;
|
||||
|
||||
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;
|
||||
|
||||
ov_callbacks default_callbacks;
|
||||
|
||||
if (!callbacks_p) {
|
||||
default_callbacks.read_func = read_func;
|
||||
default_callbacks.seek_func = seek_func;
|
||||
default_callbacks.close_func = close_func;
|
||||
default_callbacks.tell_func = tell_func;
|
||||
|
||||
if (vgm_inf->scd_xor != 0) {
|
||||
default_callbacks.read_func = read_func_scd;
|
||||
}
|
||||
|
||||
callbacks_p = &default_callbacks;
|
||||
}
|
||||
|
||||
temp_streamfile.streamfile = streamFile;
|
||||
temp_streamfile.offset = 0;
|
||||
temp_streamfile.size = get_streamfile_size(temp_streamfile.streamfile);
|
||||
temp_streamfile.other_header_bytes = other_header_bytes;
|
||||
temp_streamfile.scd_xor = vgm_inf->scd_xor;
|
||||
temp_streamfile.scd_xor_len = vgm_inf->scd_xor_len;
|
||||
|
||||
/* can we open this as a proper ogg vorbis file? */
|
||||
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 open a STREAMFILE just 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;
|
||||
data->ov_streamfile.offset = 0;
|
||||
data->ov_streamfile.size = get_streamfile_size(data->ov_streamfile.streamfile);
|
||||
data->ov_streamfile.other_header_bytes = other_header_bytes;
|
||||
data->ov_streamfile.scd_xor = vgm_inf->scd_xor;
|
||||
data->ov_streamfile.scd_xor_len = vgm_inf->scd_xor_len;
|
||||
|
||||
/* 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;
|
||||
inited_ovf = 1;
|
||||
|
||||
data->bitstream = DEFAULT_BITSTREAM;
|
||||
|
||||
info = ov_info(ovf,DEFAULT_BITSTREAM);
|
||||
|
||||
/* grab the comments */
|
||||
{
|
||||
int i;
|
||||
vorbis_comment *comment;
|
||||
|
||||
comment = ov_comment(ovf,DEFAULT_BITSTREAM);
|
||||
|
||||
/* search for a "loop_start" comment */
|
||||
for (i=0;i<comment->comments;i++) {
|
||||
if (strstr(comment->user_comments[i],"loop_start=")==
|
||||
comment->user_comments[i] ||
|
||||
strstr(comment->user_comments[i],"LOOP_START=")==
|
||||
comment->user_comments[i] ||
|
||||
strstr(comment->user_comments[i],"COMMENT=LOOPPOINT=")==
|
||||
comment->user_comments[i] ||
|
||||
strstr(comment->user_comments[i],"LOOPSTART=")==
|
||||
comment->user_comments[i] ||
|
||||
strstr(comment->user_comments[i],"um3.stream.looppoint.start=")==
|
||||
comment->user_comments[i] ||
|
||||
strstr(comment->user_comments[i],"LOOP_BEGIN=")==
|
||||
comment->user_comments[i] ||
|
||||
strstr(comment->user_comments[i],"LoopStart=")==
|
||||
comment->user_comments[i]
|
||||
) {
|
||||
loop_start=atol(strrchr(comment->user_comments[i],'=')+1);
|
||||
if (loop_start >= 0)
|
||||
loop_flag=1;
|
||||
}
|
||||
else if (strstr(comment->user_comments[i],"LOOPLENGTH=")==
|
||||
comment->user_comments[i]) {
|
||||
loop_length=atol(strrchr(comment->user_comments[i],'=')+1);
|
||||
loop_length_found=1;
|
||||
}
|
||||
else if (strstr(comment->user_comments[i],"title=-lps")==
|
||||
comment->user_comments[i]) {
|
||||
loop_start=atol(comment->user_comments[i]+10);
|
||||
if (loop_start >= 0)
|
||||
loop_flag=1;
|
||||
}
|
||||
else if (strstr(comment->user_comments[i],"album=-lpe")==
|
||||
comment->user_comments[i]) {
|
||||
loop_end=atol(comment->user_comments[i]+10);
|
||||
loop_flag=1;
|
||||
loop_end_found=1;
|
||||
}
|
||||
else if (strstr(comment->user_comments[i],"LoopEnd=")==
|
||||
comment->user_comments[i]) {
|
||||
if(loop_flag) {
|
||||
loop_length=atol(strrchr(comment->user_comments[i],'=')+1)-loop_start;
|
||||
loop_length_found=1;
|
||||
}
|
||||
}
|
||||
else if (strstr(comment->user_comments[i],"LOOP_END=")==
|
||||
comment->user_comments[i]) {
|
||||
if(loop_flag) {
|
||||
loop_length=atol(strrchr(comment->user_comments[i],'=')+1)-loop_start;
|
||||
loop_length_found=1;
|
||||
}
|
||||
}
|
||||
else if (strstr(comment->user_comments[i],"lp=")==
|
||||
comment->user_comments[i]) {
|
||||
sscanf(strrchr(comment->user_comments[i],'=')+1,"%d,%d",
|
||||
&loop_start,&loop_end);
|
||||
loop_flag=1;
|
||||
loop_end_found=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(info->channels,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* store our fun extra datas */
|
||||
vgmstream->codec_data = data;
|
||||
|
||||
/* fill in the vital statistics */
|
||||
vgmstream->channels = info->channels;
|
||||
vgmstream->sample_rate = info->rate;
|
||||
|
||||
/* let's play the whole file */
|
||||
vgmstream->num_samples = ov_pcm_total(ovf,-1);
|
||||
|
||||
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;
|
||||
}
|
||||
vgmstream->coding_type = coding_ogg_vorbis;
|
||||
vgmstream->layout_type = vgm_inf->layout_type;
|
||||
vgmstream->meta_type = vgm_inf->meta_type;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
if (data) {
|
||||
if (inited_ovf)
|
||||
ov_clear(&data->ogg_vorbis_file);
|
||||
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
|
||||
#include "../vgmstream.h"
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "meta.h"
|
||||
#include "../util.h"
|
||||
#include <vorbis/vorbisfile.h>
|
||||
|
||||
|
||||
#define DEFAULT_BITSTREAM 0
|
||||
|
||||
static size_t read_func(void *ptr, size_t size, size_t nmemb, void * datasource)
|
||||
{
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
size_t items_read;
|
||||
|
||||
size_t bytes_read;
|
||||
|
||||
bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb,
|
||||
ov_streamfile->streamfile);
|
||||
|
||||
items_read = bytes_read / size;
|
||||
|
||||
ov_streamfile->offset += items_read * size;
|
||||
|
||||
return items_read;
|
||||
}
|
||||
|
||||
static size_t read_func_um3(void *ptr, size_t size, size_t nmemb, void * datasource)
|
||||
{
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
size_t items_read;
|
||||
|
||||
size_t bytes_read;
|
||||
|
||||
bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb,
|
||||
ov_streamfile->streamfile);
|
||||
|
||||
items_read = bytes_read / size;
|
||||
|
||||
/* first 0x800 bytes of um3 are xor'd with 0xff */
|
||||
if (ov_streamfile->offset < 0x800) {
|
||||
int num_crypt = 0x800-ov_streamfile->offset;
|
||||
int i;
|
||||
|
||||
if (num_crypt > bytes_read) num_crypt=bytes_read;
|
||||
for (i=0;i<num_crypt;i++)
|
||||
((uint8_t*)ptr)[i] ^= 0xff;
|
||||
}
|
||||
|
||||
ov_streamfile->offset += items_read * size;
|
||||
|
||||
return items_read;
|
||||
}
|
||||
|
||||
static size_t read_func_kovs(void *ptr, size_t size, size_t nmemb, void * datasource)
|
||||
{
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
size_t items_read;
|
||||
|
||||
size_t bytes_read;
|
||||
|
||||
bytes_read = read_streamfile(ptr, ov_streamfile->offset+ov_streamfile->other_header_bytes, size * nmemb,
|
||||
ov_streamfile->streamfile);
|
||||
|
||||
items_read = bytes_read / size;
|
||||
|
||||
/* first 0x100 bytes of KOVS are xor'd with offset */
|
||||
if (ov_streamfile->offset < 0x100) {
|
||||
int i;
|
||||
|
||||
for (i=ov_streamfile->offset;i<0x100;i++)
|
||||
((uint8_t*)ptr)[i-ov_streamfile->offset] ^= i;
|
||||
}
|
||||
|
||||
ov_streamfile->offset += items_read * size;
|
||||
|
||||
return items_read;
|
||||
}
|
||||
|
||||
static size_t read_func_scd(void *ptr, size_t size, size_t nmemb, void * datasource)
|
||||
{
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
size_t items_read;
|
||||
|
||||
size_t bytes_read;
|
||||
|
||||
bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb,
|
||||
ov_streamfile->streamfile);
|
||||
|
||||
items_read = bytes_read / size;
|
||||
|
||||
/* first bytes are xor'd with a constant byte */
|
||||
if (ov_streamfile->offset < ov_streamfile->scd_xor_len) {
|
||||
int num_crypt = ov_streamfile->scd_xor_len-ov_streamfile->offset;
|
||||
int i;
|
||||
|
||||
if (num_crypt > bytes_read) num_crypt=bytes_read;
|
||||
for (i=0;i<num_crypt;i++)
|
||||
((uint8_t*)ptr)[i] ^= ov_streamfile->scd_xor;
|
||||
}
|
||||
|
||||
ov_streamfile->offset += items_read * size;
|
||||
|
||||
return items_read;
|
||||
}
|
||||
|
||||
static size_t read_func_psych(void *ptr, size_t size, size_t nmemb, void * datasource)
|
||||
{
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
size_t items_read;
|
||||
|
||||
size_t bytes_read;
|
||||
size_t i;
|
||||
|
||||
bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb,
|
||||
ov_streamfile->streamfile);
|
||||
|
||||
/* add 0x23 ('#') */
|
||||
for (i=0;i<bytes_read;i++)
|
||||
((uint8_t*)ptr)[i] += 0x23;
|
||||
|
||||
items_read = bytes_read / size;
|
||||
|
||||
ov_streamfile->offset += items_read * size;
|
||||
|
||||
return items_read;
|
||||
}
|
||||
|
||||
static int seek_func(void *datasource, ogg_int64_t offset, int whence) {
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
ogg_int64_t base_offset;
|
||||
ogg_int64_t 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 - ov_streamfile->other_header_bytes;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
|
||||
new_offset = base_offset + offset;
|
||||
if (new_offset < 0 || new_offset > (ov_streamfile->size - ov_streamfile->other_header_bytes)) {
|
||||
return -1;
|
||||
} else {
|
||||
ov_streamfile->offset = new_offset;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static long tell_func(void * datasource) {
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
return ov_streamfile->offset;
|
||||
}
|
||||
|
||||
/* setting close_func in ov_callbacks to NULL doesn't seem to work */
|
||||
static int close_func(void * datasource) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Ogg Vorbis, by way of libvorbisfile */
|
||||
|
||||
VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
||||
char filename[PATH_LIMIT];
|
||||
|
||||
ov_callbacks callbacks;
|
||||
|
||||
off_t other_header_bytes = 0;
|
||||
int um3_ogg = 0;
|
||||
int kovs_ogg = 0;
|
||||
int psych_ogg = 0;
|
||||
|
||||
vgm_vorbis_info_t inf;
|
||||
memset(&inf, 0, sizeof(inf));
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
|
||||
/* It is only interesting to use oggs with vgmstream if they are looped.
|
||||
To prevent such files from being played by other plugins and such they
|
||||
may be renamed to .logg. This meta reader should still support .ogg,
|
||||
though. */
|
||||
if (strcasecmp("logg",filename_extension(filename)) &&
|
||||
strcasecmp("ogg",filename_extension(filename))) {
|
||||
if (!strcasecmp("um3",filename_extension(filename))) {
|
||||
um3_ogg = 1;
|
||||
} else if (!strcasecmp("kovs",filename_extension(filename))) {
|
||||
kovs_ogg = 1;
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* not all um3-ogg are crypted */
|
||||
if (um3_ogg && read_32bitBE(0x0,streamFile)==0x4f676753) {
|
||||
um3_ogg = 0;
|
||||
}
|
||||
|
||||
/* use KOVS header */
|
||||
if (kovs_ogg) {
|
||||
if (read_32bitBE(0x0,streamFile)!=0x4b4f5653) { /* "KOVS" */
|
||||
goto fail;
|
||||
}
|
||||
if (read_32bitLE(0x8,streamFile)!=0) {
|
||||
inf.loop_start = read_32bitLE(0x8,streamFile);
|
||||
inf.loop_flag = 1;
|
||||
}
|
||||
|
||||
other_header_bytes = 0x20;
|
||||
}
|
||||
|
||||
/* detect Psychic Software obfuscation (as seen in "Darkwind") */
|
||||
if (read_32bitBE(0x0,streamFile)==0x2c444430) {
|
||||
psych_ogg = 1;
|
||||
}
|
||||
|
||||
if (um3_ogg) {
|
||||
callbacks.read_func = read_func_um3;
|
||||
} else if (kovs_ogg) {
|
||||
callbacks.read_func = read_func_kovs;
|
||||
} else if (psych_ogg) {
|
||||
callbacks.read_func = read_func_psych;
|
||||
} else {
|
||||
callbacks.read_func = read_func;
|
||||
}
|
||||
callbacks.seek_func = seek_func;
|
||||
callbacks.close_func = close_func;
|
||||
callbacks.tell_func = tell_func;
|
||||
|
||||
if (um3_ogg) {
|
||||
inf.meta_type = meta_um3_ogg;
|
||||
} else if (kovs_ogg) {
|
||||
inf.meta_type = meta_KOVS_ogg;
|
||||
} else if (psych_ogg) {
|
||||
inf.meta_type = meta_psych_ogg;
|
||||
} else {
|
||||
inf.meta_type = meta_ogg_vorbis;
|
||||
}
|
||||
|
||||
inf.layout_type = layout_ogg_vorbis;
|
||||
|
||||
return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, &callbacks, other_header_bytes, &inf);
|
||||
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const char * filename, ov_callbacks *callbacks_p, off_t other_header_bytes, const vgm_vorbis_info_t *vgm_inf) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
|
||||
OggVorbis_File temp_ovf;
|
||||
ogg_vorbis_streamfile temp_streamfile;
|
||||
|
||||
ogg_vorbis_codec_data * data = NULL;
|
||||
OggVorbis_File *ovf;
|
||||
int inited_ovf = 0;
|
||||
vorbis_info *info;
|
||||
|
||||
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;
|
||||
|
||||
ov_callbacks default_callbacks;
|
||||
|
||||
if (!callbacks_p) {
|
||||
default_callbacks.read_func = read_func;
|
||||
default_callbacks.seek_func = seek_func;
|
||||
default_callbacks.close_func = close_func;
|
||||
default_callbacks.tell_func = tell_func;
|
||||
|
||||
if (vgm_inf->scd_xor != 0) {
|
||||
default_callbacks.read_func = read_func_scd;
|
||||
}
|
||||
|
||||
callbacks_p = &default_callbacks;
|
||||
}
|
||||
|
||||
temp_streamfile.streamfile = streamFile;
|
||||
temp_streamfile.offset = 0;
|
||||
temp_streamfile.size = get_streamfile_size(temp_streamfile.streamfile);
|
||||
temp_streamfile.other_header_bytes = other_header_bytes;
|
||||
temp_streamfile.scd_xor = vgm_inf->scd_xor;
|
||||
temp_streamfile.scd_xor_len = vgm_inf->scd_xor_len;
|
||||
|
||||
/* can we open this as a proper ogg vorbis file? */
|
||||
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 open a STREAMFILE just 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;
|
||||
data->ov_streamfile.offset = 0;
|
||||
data->ov_streamfile.size = get_streamfile_size(data->ov_streamfile.streamfile);
|
||||
data->ov_streamfile.other_header_bytes = other_header_bytes;
|
||||
data->ov_streamfile.scd_xor = vgm_inf->scd_xor;
|
||||
data->ov_streamfile.scd_xor_len = vgm_inf->scd_xor_len;
|
||||
|
||||
/* 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;
|
||||
inited_ovf = 1;
|
||||
|
||||
data->bitstream = DEFAULT_BITSTREAM;
|
||||
|
||||
info = ov_info(ovf,DEFAULT_BITSTREAM);
|
||||
|
||||
/* grab the comments */
|
||||
{
|
||||
int i;
|
||||
vorbis_comment *comment;
|
||||
|
||||
comment = ov_comment(ovf,DEFAULT_BITSTREAM);
|
||||
|
||||
/* search for a "loop_start" comment */
|
||||
for (i=0;i<comment->comments;i++) {
|
||||
if (strstr(comment->user_comments[i],"loop_start=")==
|
||||
comment->user_comments[i] ||
|
||||
strstr(comment->user_comments[i],"LOOP_START=")==
|
||||
comment->user_comments[i] ||
|
||||
strstr(comment->user_comments[i],"COMMENT=LOOPPOINT=")==
|
||||
comment->user_comments[i] ||
|
||||
strstr(comment->user_comments[i],"LOOPSTART=")==
|
||||
comment->user_comments[i] ||
|
||||
strstr(comment->user_comments[i],"um3.stream.looppoint.start=")==
|
||||
comment->user_comments[i] ||
|
||||
strstr(comment->user_comments[i],"LOOP_BEGIN=")==
|
||||
comment->user_comments[i] ||
|
||||
strstr(comment->user_comments[i],"LoopStart=")==
|
||||
comment->user_comments[i]
|
||||
) {
|
||||
loop_start=atol(strrchr(comment->user_comments[i],'=')+1);
|
||||
if (loop_start >= 0)
|
||||
loop_flag=1;
|
||||
}
|
||||
else if (strstr(comment->user_comments[i],"LOOPLENGTH=")==
|
||||
comment->user_comments[i]) {
|
||||
loop_length=atol(strrchr(comment->user_comments[i],'=')+1);
|
||||
loop_length_found=1;
|
||||
}
|
||||
else if (strstr(comment->user_comments[i],"title=-lps")==
|
||||
comment->user_comments[i]) {
|
||||
loop_start=atol(comment->user_comments[i]+10);
|
||||
if (loop_start >= 0)
|
||||
loop_flag=1;
|
||||
}
|
||||
else if (strstr(comment->user_comments[i],"album=-lpe")==
|
||||
comment->user_comments[i]) {
|
||||
loop_end=atol(comment->user_comments[i]+10);
|
||||
loop_flag=1;
|
||||
loop_end_found=1;
|
||||
}
|
||||
else if (strstr(comment->user_comments[i],"LoopEnd=")==
|
||||
comment->user_comments[i]) {
|
||||
if(loop_flag) {
|
||||
loop_length=atol(strrchr(comment->user_comments[i],'=')+1)-loop_start;
|
||||
loop_length_found=1;
|
||||
}
|
||||
}
|
||||
else if (strstr(comment->user_comments[i],"LOOP_END=")==
|
||||
comment->user_comments[i]) {
|
||||
if(loop_flag) {
|
||||
loop_length=atol(strrchr(comment->user_comments[i],'=')+1)-loop_start;
|
||||
loop_length_found=1;
|
||||
}
|
||||
}
|
||||
else if (strstr(comment->user_comments[i],"lp=")==
|
||||
comment->user_comments[i]) {
|
||||
sscanf(strrchr(comment->user_comments[i],'=')+1,"%d,%d",
|
||||
&loop_start,&loop_end);
|
||||
loop_flag=1;
|
||||
loop_end_found=1;
|
||||
}
|
||||
else if (strstr(comment->user_comments[i],"COMMENT=loop(")==
|
||||
comment->user_comments[i]) {
|
||||
sscanf(strrchr(comment->user_comments[i],'(')+1,"%d,%d",
|
||||
&loop_start,&loop_end);
|
||||
loop_flag=1;
|
||||
loop_end_found=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(info->channels,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* store our fun extra datas */
|
||||
vgmstream->codec_data = data;
|
||||
|
||||
/* fill in the vital statistics */
|
||||
vgmstream->channels = info->channels;
|
||||
vgmstream->sample_rate = info->rate;
|
||||
|
||||
/* let's play the whole file */
|
||||
vgmstream->num_samples = ov_pcm_total(ovf,-1);
|
||||
|
||||
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;
|
||||
}
|
||||
vgmstream->coding_type = coding_ogg_vorbis;
|
||||
vgmstream->layout_type = vgm_inf->layout_type;
|
||||
vgmstream->meta_type = vgm_inf->meta_type;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
if (data) {
|
||||
if (inited_ovf)
|
||||
ov_clear(&data->ogg_vorbis_file);
|
||||
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
|
||||
|
@ -360,6 +360,13 @@ VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile, int do_dfs) {
|
||||
if (vgmstream) {
|
||||
/* these are little hacky checks */
|
||||
|
||||
/* fail if there is nothing to play
|
||||
* (without this check vgmstream can generate empty files) */
|
||||
if ( vgmstream->num_samples==0 ) {
|
||||
close_vgmstream(vgmstream);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* everything should have a reasonable sample rate
|
||||
* (a verification of the metadata) */
|
||||
if (!check_sample_rate(vgmstream->sample_rate)) {
|
||||
|
@ -194,6 +194,7 @@ const char *const VgmStreamPlugin::exts[] =
|
||||
"sfx",
|
||||
"sgb",
|
||||
"sgd",
|
||||
"sgx",
|
||||
"sl3",
|
||||
"sli",
|
||||
"smp",
|
||||
|
Loading…
x
Reference in New Issue
Block a user