Clean Ogg Vorbis code and IO/decryption callbacks for future changes

This commit is contained in:
bnnm 2018-01-10 21:12:23 +01:00
parent bcad321b6d
commit 7134610495
3 changed files with 231 additions and 327 deletions

View File

@ -99,9 +99,8 @@ typedef struct {
meta_t meta_type; meta_t meta_type;
layout_t layout_type; layout_t layout_type;
/* XOR setup (SCD) */ /* decryption setup */
int decryption_enabled; void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource);
void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read);
uint8_t scd_xor; uint8_t scd_xor;
off_t scd_xor_length; off_t scd_xor_length;

View File

@ -1,100 +1,23 @@
#include "../vgmstream.h" #include "../vgmstream.h"
#ifdef VGM_USE_VORBIS #ifdef VGM_USE_VORBIS
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "meta.h" #include "meta.h"
#include "../util.h"
#include <vorbis/vorbisfile.h> #include <vorbis/vorbisfile.h>
#define OGG_DEFAULT_BITSTREAM 0
#define DEFAULT_BITSTREAM 0 static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void * datasource) {
static size_t read_func(void *ptr, size_t size, size_t nmemb, void * datasource)
{
ogg_vorbis_streamfile * const ov_streamfile = datasource; ogg_vorbis_streamfile * const ov_streamfile = datasource;
size_t items_read; size_t bytes_read, 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);
bytes_read = read_streamfile(ptr, ov_streamfile->offset + ov_streamfile->other_header_bytes, size * nmemb, ov_streamfile->streamfile);
items_read = bytes_read / size; items_read = bytes_read / size;
/* may be encrypted */ /* may be encrypted */
if (ov_streamfile->decryption_enabled) { if (ov_streamfile->decryption_callback) {
ov_streamfile->decryption_callback(ptr, size, nmemb, ov_streamfile, bytes_read); ov_streamfile->decryption_callback(ptr, size, items_read, ov_streamfile);
} }
ov_streamfile->offset += items_read * size; ov_streamfile->offset += items_read * size;
@ -102,32 +25,9 @@ static size_t read_func_scd(void *ptr, size_t size, size_t nmemb, void * datasou
return items_read; return items_read;
} }
static size_t read_func_psych(void *ptr, size_t size, size_t nmemb, void * datasource) static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) {
{
ogg_vorbis_streamfile * const ov_streamfile = datasource; ogg_vorbis_streamfile * const ov_streamfile = datasource;
size_t items_read; ogg_int64_t base_offset, new_offset;
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) { switch (whence) {
case SEEK_SET: case SEEK_SET:
@ -144,107 +44,141 @@ static int seek_func(void *datasource, ogg_int64_t offset, int whence) {
break; break;
} }
new_offset = base_offset + offset; new_offset = base_offset + offset;
if (new_offset < 0 || new_offset > (ov_streamfile->size - ov_streamfile->other_header_bytes)) { if (new_offset < 0 || new_offset > (ov_streamfile->size - ov_streamfile->other_header_bytes)) {
return -1; return -1; /* *must* return -1 if stream is unseekable */
} else { } else {
ov_streamfile->offset = new_offset; ov_streamfile->offset = new_offset;
return 0; return 0;
} }
} }
static long tell_func(void * datasource) { static long ov_tell_func(void * datasource) {
ogg_vorbis_streamfile * const ov_streamfile = datasource; ogg_vorbis_streamfile * const ov_streamfile = datasource;
return ov_streamfile->offset; return ov_streamfile->offset;
} }
/* setting close_func in ov_callbacks to NULL doesn't seem to work */ static int ov_close_func(void * datasource) {
static int 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; return 0;
} }
/* Ogg Vorbis, by way of libvorbisfile */
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;
/* 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;
for (i = 0; i < num_crypt; i++)
((uint8_t*)ptr)[i] ^= 0xff;
}
}
static void kovs_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
size_t bytes_read = size*nmemb;
ogg_vorbis_streamfile * const ov_streamfile = datasource;
int i;
/* 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;
}
}
}
static void psychic_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
size_t bytes_read = size*nmemb;
int i;
/* add 0x23 ('#') */
{
for (i = 0; i < bytes_read; i++)
((uint8_t*)ptr)[i] += 0x23;
}
}
/* Ogg Vorbis, by way of libvorbisfile; may contain loop comments */
VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
char filename[PATH_LIMIT]; char filename[PATH_LIMIT];
vgm_vorbis_info_t inf = {0};
off_t start_offset = 0;
ov_callbacks callbacks; int is_ogg = 0;
int is_um3 = 0;
int is_kovs = 0;
int is_psychic = 0;
off_t other_header_bytes = 0;
int um3_ogg = 0;
int kovs_ogg = 0;
int psych_ogg = 0;
vgm_vorbis_info_t inf; /* check extension */
memset(&inf, 0, sizeof(inf)); if (check_extensions(streamFile,"ogg,logg")) { /* .ogg: standard/psychic, .logg: renamed for plugins */
is_ogg = 1;
/* check extension, case insensitive */ } else if (check_extensions(streamFile,"um3")) {
streamFile->get_name(streamFile,filename,sizeof(filename)); is_um3 = 1;
} else if (check_extensions(streamFile,"kvs,kovs")) { /* .kvs: Atelier Sophie (PC), kovs: header id only? */
/* It is only interesting to use oggs with vgmstream if they are looped. is_kovs = 1;
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 (check_extensions(streamFile,"kvs,kovs")) { /* .kvs: Atelier Sophie, kovs: header id only? */
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 { } else {
callbacks.read_func = read_func; goto fail;
} }
callbacks.seek_func = seek_func; streamFile->get_name(streamFile,filename,sizeof(filename));
callbacks.close_func = close_func;
callbacks.tell_func = tell_func;
if (um3_ogg) { /* 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;
}
else if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" */
goto fail; /* not known (ex. Wwise) */
}
}
/* check "Ultramarine3" (???), may be encrypted */
if (is_um3) {
if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" */
inf.decryption_callback = um3_ogg_decryption_callback;
}
}
/* 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;
}
if (is_um3) {
inf.meta_type = meta_OGG_UM3; inf.meta_type = meta_OGG_UM3;
} else if (kovs_ogg) { } else if (is_kovs) {
inf.meta_type = meta_OGG_KOVS; inf.meta_type = meta_OGG_KOVS;
} else if (psych_ogg) { } else if (is_psychic) {
inf.meta_type = meta_OGG_PSYCH; inf.meta_type = meta_OGG_PSYCH;
} else { } else {
inf.meta_type = meta_OGG_VORBIS; inf.meta_type = meta_OGG_VORBIS;
} }
inf.layout_type = layout_ogg_vorbis; inf.layout_type = layout_ogg_vorbis;
return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, &callbacks, other_header_bytes, &inf); return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);
fail: fail:
return NULL; return NULL;
@ -252,14 +186,9 @@ fail:
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 * 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; VGMSTREAM * vgmstream = NULL;
OggVorbis_File temp_ovf;
ogg_vorbis_streamfile temp_streamfile;
ogg_vorbis_codec_data * data = NULL; ogg_vorbis_codec_data * data = NULL;
OggVorbis_File *ovf; OggVorbis_File *ovf = NULL;
int inited_ovf = 0; vorbis_info *vi;
vorbis_info *info;
int loop_flag = vgm_inf->loop_flag; int loop_flag = vgm_inf->loop_flag;
int32_t loop_start = vgm_inf->loop_start; int32_t loop_start = vgm_inf->loop_start;
@ -271,158 +200,132 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const ch
ov_callbacks default_callbacks; ov_callbacks default_callbacks;
if (!callbacks_p) { if (!callbacks_p) {
default_callbacks.read_func = read_func; default_callbacks.read_func = ov_read_func;
default_callbacks.seek_func = seek_func; default_callbacks.seek_func = ov_seek_func;
default_callbacks.close_func = close_func; default_callbacks.close_func = ov_close_func;
default_callbacks.tell_func = tell_func; default_callbacks.tell_func = ov_tell_func;
if (vgm_inf->decryption_enabled) {
default_callbacks.read_func = read_func_scd;
}
callbacks_p = &default_callbacks; callbacks_p = &default_callbacks;
} }
temp_streamfile.streamfile = streamFile; /* test if this is a proper Ogg Vorbis file, with the current (from init_x) STREAMFILE */
temp_streamfile.offset = 0; {
temp_streamfile.size = get_streamfile_size(temp_streamfile.streamfile); OggVorbis_File temp_ovf;
temp_streamfile.other_header_bytes = other_header_bytes; ogg_vorbis_streamfile temp_streamfile;
temp_streamfile.decryption_enabled = vgm_inf->decryption_enabled;
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;
/* can we open this as a proper ogg vorbis file? */ temp_streamfile.streamfile = streamFile;
memset(&temp_ovf, 0, sizeof(temp_ovf)); temp_streamfile.offset = 0;
if (ov_test_callbacks(&temp_streamfile, &temp_ovf, NULL, temp_streamfile.size = get_streamfile_size(temp_streamfile.streamfile);
0, *callbacks_p)) goto fail; temp_streamfile.other_header_bytes = other_header_bytes;
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;
/* we have to close this as it has the init_vgmstream meta-reading /* open the ogg vorbis file for testing */
STREAMFILE */ memset(&temp_ovf, 0, sizeof(temp_ovf));
ov_clear(&temp_ovf); if (ov_test_callbacks(&temp_streamfile, &temp_ovf, NULL, 0, *callbacks_p))
goto fail;
/* proceed to open a STREAMFILE just for this stream */ /* we have to close this as it has the init_vgmstream meta-reading STREAMFILE */
data = calloc(1,sizeof(ogg_vorbis_codec_data)); ov_clear(&temp_ovf);
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.decryption_enabled = vgm_inf->decryption_enabled;
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;
/* open the ogg vorbis file for real */ /* proceed to init codec_data and reopen a STREAMFILE for this stream */
if (ov_open_callbacks(&data->ov_streamfile, &data->ogg_vorbis_file, NULL, {
0, *callbacks_p)) goto fail; data = calloc(1,sizeof(ogg_vorbis_codec_data));
ovf = &data->ogg_vorbis_file; if (!data) goto fail;
inited_ovf = 1;
data->bitstream = DEFAULT_BITSTREAM; data->ov_streamfile.streamfile = streamFile->open(streamFile,filename, STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!data->ov_streamfile.streamfile) goto fail;
info = ov_info(ovf,DEFAULT_BITSTREAM); 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.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;
/* grab the comments */ /* 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 */
{ {
int i; int i;
vorbis_comment *comment; vorbis_comment *comment = ov_comment(ovf,OGG_DEFAULT_BITSTREAM);
comment = ov_comment(ovf,DEFAULT_BITSTREAM); for (i = 0; i < comment->comments; i++) {
const char * user_comment = comment->user_comments[i];
/* search for a "loop_start" comment */ if (strstr(user_comment,"loop_start=")==user_comment || /* PSO4 */
for (i=0;i<comment->comments;i++) { strstr(user_comment,"LOOP_START=")==user_comment || /* PSO4 */
if (strstr(comment->user_comments[i],"loop_start=")== strstr(user_comment,"COMMENT=LOOPPOINT=")==user_comment ||
comment->user_comments[i] || strstr(user_comment,"LOOPSTART=")==user_comment ||
strstr(comment->user_comments[i],"LOOP_START=")== strstr(user_comment,"um3.stream.looppoint.start=")==user_comment ||
comment->user_comments[i] || strstr(user_comment,"LOOP_BEGIN=")==user_comment || /* Hatsune Miku: Project Diva F (PS3) */
strstr(comment->user_comments[i],"COMMENT=LOOPPOINT=")== strstr(user_comment,"LoopStart=")==user_comment) { /* Devil May Cry 4 (PC) */
comment->user_comments[i] || loop_start = atol(strrchr(user_comment,'=')+1);
strstr(comment->user_comments[i],"LOOPSTART=")== loop_flag = (loop_start >= 0);
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=")== else if (strstr(user_comment,"LOOPLENGTH=")==user_comment) {/* (LOOPSTART pair) */
comment->user_comments[i]) { loop_length = atol(strrchr(user_comment,'=')+1);
loop_length=atol(strrchr(comment->user_comments[i],'=')+1); loop_length_found = 1;
loop_length_found=1;
} }
else if (strstr(comment->user_comments[i],"title=-lps")== else if (strstr(user_comment,"title=-lps")==user_comment) { /* Memories Off #5 (PC) */
comment->user_comments[i]) { loop_start = atol(user_comment+10);
loop_start=atol(comment->user_comments[i]+10); loop_flag = (loop_start >= 0);
if (loop_start >= 0)
loop_flag=1;
} }
else if (strstr(comment->user_comments[i],"album=-lpe")== else if (strstr(user_comment,"album=-lpe")==user_comment) { /* (title=-lps pair) */
comment->user_comments[i]) { loop_end = atol(user_comment+10);
loop_end=atol(comment->user_comments[i]+10); loop_flag = 1;
loop_flag=1; loop_end_found = 1;
loop_end_found=1;
} }
else if (strstr(comment->user_comments[i],"LoopEnd=")== else if (strstr(user_comment,"LoopEnd=")==user_comment) { /* (LoopStart pair) */
comment->user_comments[i]) { if(loop_flag) {
if(loop_flag) { loop_length = atol(strrchr(user_comment,'=')+1)-loop_start;
loop_length=atol(strrchr(comment->user_comments[i],'=')+1)-loop_start; loop_length_found = 1;
loop_length_found=1; }
}
} }
else if (strstr(comment->user_comments[i],"LOOP_END=")== else if (strstr(user_comment,"LOOP_END=")==user_comment) { /* (LOOP_BEGIN pair) */
comment->user_comments[i]) { if(loop_flag) {
if(loop_flag) { loop_length = atol(strrchr(user_comment,'=')+1)-loop_start;
loop_length=atol(strrchr(comment->user_comments[i],'=')+1)-loop_start; loop_length_found = 1;
loop_length_found=1; }
}
} }
else if (strstr(comment->user_comments[i],"lp=")== else if (strstr(user_comment,"lp=")==user_comment) {
comment->user_comments[i]) { sscanf(strrchr(user_comment,'=')+1,"%d,%d", &loop_start,&loop_end);
sscanf(strrchr(comment->user_comments[i],'=')+1,"%d,%d", loop_flag = 1;
&loop_start,&loop_end); loop_end_found = 1;
loop_flag=1;
loop_end_found=1;
} }
else if (strstr(comment->user_comments[i],"LOOPDEFS=")== else if (strstr(user_comment,"LOOPDEFS=")==user_comment) { /* Fairy Fencer F: Advent Dark Force */
comment->user_comments[i]) { sscanf(strrchr(user_comment,'=')+1,"%d,%d", &loop_start,&loop_end);
sscanf(strrchr(comment->user_comments[i],'=')+1,"%d,%d", loop_flag = 1;
&loop_start,&loop_end); loop_end_found = 1;
loop_flag=1;
loop_end_found=1;
} }
else if (strstr(comment->user_comments[i],"COMMENT=loop(")== else if (strstr(user_comment,"COMMENT=loop(")==user_comment) { /* Zero Time Dilemma (PC) */
comment->user_comments[i]) { sscanf(strrchr(user_comment,'(')+1,"%d,%d", &loop_start,&loop_end);
sscanf(strrchr(comment->user_comments[i],'(')+1,"%d,%d", loop_flag = 1;
&loop_start,&loop_end); loop_end_found = 1;
loop_flag=1;
loop_end_found=1;
} }
} }
} }
/* build the VGMSTREAM */ /* build the VGMSTREAM */
vgmstream = allocate_vgmstream(info->channels,loop_flag); vgmstream = allocate_vgmstream(vi->channels,loop_flag);
if (!vgmstream) goto fail; if (!vgmstream) goto fail;
/* store our fun extra datas */ vgmstream->codec_data = data; /* store our fun extra datas */
vgmstream->codec_data = data; vgmstream->channels = vi->channels;
vgmstream->sample_rate = vi->rate;
/* 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);
vgmstream->num_samples = ov_pcm_total(ovf,-1); /* let libvorbisfile find total 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)
@ -436,17 +339,18 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, const ch
if (vgmstream->loop_end_sample > vgmstream->num_samples) if (vgmstream->loop_end_sample > vgmstream->num_samples)
vgmstream->loop_end_sample = vgmstream->num_samples; vgmstream->loop_end_sample = vgmstream->num_samples;
} }
vgmstream->coding_type = coding_ogg_vorbis; vgmstream->coding_type = coding_ogg_vorbis;
vgmstream->layout_type = vgm_inf->layout_type; vgmstream->layout_type = vgm_inf->layout_type;
vgmstream->meta_type = vgm_inf->meta_type; vgmstream->meta_type = vgm_inf->meta_type;
return vgmstream; return vgmstream;
/* clean up anything we may have opened */
fail: fail:
/* clean up anything we may have opened */
if (data) { if (data) {
if (inited_ovf) if (ovf)
ov_clear(&data->ogg_vorbis_file); ov_clear(&data->ogg_vorbis_file);//same as ovf
if (data->ov_streamfile.streamfile) if (data->ov_streamfile.streamfile)
close_streamfile(data->ov_streamfile.streamfile); close_streamfile(data->ov_streamfile.streamfile);
free(data); free(data);

View File

@ -4,8 +4,8 @@
#ifdef VGM_USE_VORBIS #ifdef VGM_USE_VORBIS
static void scd_ogg_decrypt_v2_callback(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read); static void scd_ogg_v2_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource);
static void scd_ogg_decrypt_v3_callback(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read); static void scd_ogg_v3_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource);
#endif #endif
/* SCD - Square-Enix games (FF XIII, XIV) */ /* SCD - Square-Enix games (FF XIII, XIV) */
@ -178,14 +178,12 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
return NULL; /* not actually encrypted, happens but should be handled above */ return NULL; /* not actually encrypted, happens but should be handled above */
if (xor_version == 2) { /* header is XOR'ed using byte */ if (xor_version == 2) { /* header is XOR'ed using byte */
inf.decryption_enabled = 1; inf.decryption_callback = scd_ogg_v2_decryption_callback;
inf.decryption_callback = scd_ogg_decrypt_v2_callback;
inf.scd_xor = xor_byte; inf.scd_xor = xor_byte;
inf.scd_xor_length = vorb_header_size; inf.scd_xor_length = vorb_header_size;
} }
else if (xor_version == 3) { /* full file is XOR'ed using table */ else if (xor_version == 3) { /* full file is XOR'ed using table */
inf.decryption_enabled = 1; inf.decryption_callback = scd_ogg_v3_decryption_callback;
inf.decryption_callback = scd_ogg_decrypt_v3_callback;
inf.scd_xor = stream_size & 0xFF; /* xor_byte is not used? (also there is data at +0x03) */ inf.scd_xor = stream_size & 0xFF; /* xor_byte is not used? (also there is data at +0x03) */
inf.scd_xor_length = stream_size; inf.scd_xor_length = stream_size;
} }
@ -412,7 +410,8 @@ fail:
#ifdef VGM_USE_VORBIS #ifdef VGM_USE_VORBIS
static void scd_ogg_decrypt_v2_callback(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read) { static void scd_ogg_v2_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
size_t bytes_read = size*nmemb;
ogg_vorbis_streamfile * ov_streamfile = (ogg_vorbis_streamfile*)datasource; ogg_vorbis_streamfile * ov_streamfile = (ogg_vorbis_streamfile*)datasource;
/* header is XOR'd with a constant byte */ /* header is XOR'd with a constant byte */
@ -429,9 +428,9 @@ static void scd_ogg_decrypt_v2_callback(void *ptr, size_t size, size_t nmemb, vo
} }
} }
static void scd_ogg_decrypt_v3_callback(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read) { static void scd_ogg_v3_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
/* V3 decryption table found in the .exe */ /* V3 decryption table found in the .exe of FF XIV Heavensward */
static const uint8_t scd_ogg_v3_lookuptable[256] = { /* FF XIV Heavensward */ static const uint8_t scd_ogg_v3_lookuptable[256] = {
0x3A, 0x32, 0x32, 0x32, 0x03, 0x7E, 0x12, 0xF7, 0xB2, 0xE2, 0xA2, 0x67, 0x32, 0x32, 0x22, 0x32, // 00-0F 0x3A, 0x32, 0x32, 0x32, 0x03, 0x7E, 0x12, 0xF7, 0xB2, 0xE2, 0xA2, 0x67, 0x32, 0x32, 0x22, 0x32, // 00-0F
0x32, 0x52, 0x16, 0x1B, 0x3C, 0xA1, 0x54, 0x7B, 0x1B, 0x97, 0xA6, 0x93, 0x1A, 0x4B, 0xAA, 0xA6, // 10-1F 0x32, 0x52, 0x16, 0x1B, 0x3C, 0xA1, 0x54, 0x7B, 0x1B, 0x97, 0xA6, 0x93, 0x1A, 0x4B, 0xAA, 0xA6, // 10-1F
0x7A, 0x7B, 0x1B, 0x97, 0xA6, 0xF7, 0x02, 0xBB, 0xAA, 0xA6, 0xBB, 0xF7, 0x2A, 0x51, 0xBE, 0x03, // 20-2F 0x7A, 0x7B, 0x1B, 0x97, 0xA6, 0xF7, 0x02, 0xBB, 0xAA, 0xA6, 0xBB, 0xF7, 0x2A, 0x51, 0xBE, 0x03, // 20-2F
@ -449,23 +448,25 @@ static void scd_ogg_decrypt_v3_callback(void *ptr, size_t size, size_t nmemb, vo
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
}; };
size_t bytes_read = size*nmemb;
ogg_vorbis_streamfile *ov_streamfile = (ogg_vorbis_streamfile*)datasource; ogg_vorbis_streamfile *ov_streamfile = (ogg_vorbis_streamfile*)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 (ov_streamfile->offset < ov_streamfile->scd_xor_length) {
int i, num_crypt; int i, num_crypt;
uint8_t byte1, byte2, xorByte; uint8_t byte1, byte2, xor_byte;
num_crypt = bytes_read; num_crypt = bytes_read;
byte1 = ov_streamfile->scd_xor & 0x7F; byte1 = ov_streamfile->scd_xor & 0x7F;
byte2 = ov_streamfile->scd_xor & 0x3F; byte2 = ov_streamfile->scd_xor & 0x3F;
for (i = 0; i < num_crypt; i++) { for (i = 0; i < num_crypt; i++) {
xorByte = scd_ogg_v3_lookuptable[(byte2 + ov_streamfile->offset + i) & 0xFF]; xor_byte = scd_ogg_v3_lookuptable[(byte2 + ov_streamfile->offset + i) & 0xFF];
xorByte &= 0xFF; xor_byte &= 0xFF;
xorByte ^= ((uint8_t*)ptr)[i]; xor_byte ^= ((uint8_t*)ptr)[i];
xorByte ^= byte1; xor_byte ^= byte1;
((uint8_t*)ptr)[i] = (uint8_t)xorByte; ((uint8_t*)ptr)[i] = (uint8_t)xor_byte;
} }
} }
} }