Merge pull request #174 from bnnm/scd-xvag-sngw-isd-km9

SCD, XVAG, SNGW, ISD, KM9
This commit is contained in:
Christopher Snowhill 2018-01-13 16:03:27 -08:00 committed by GitHub
commit 40ffa3e1d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 811 additions and 635 deletions

View File

@ -119,7 +119,9 @@ void decode_atrac9(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do,
/* postadjust */ //todo improve /* postadjust */ //todo improve
switch(data->config.type) { switch(data->config.type) {
case ATRAC9_XVAG: /* skip other subsong blocks in XVAG */ case ATRAC9_XVAG:
case ATRAC9_KMA9:
/* skip other subsong blocks */
if (data->config.interleave_skip && ((stream->offset - stream->channel_start_offset) % data->config.interleave_skip == 0)) { if (data->config.interleave_skip && ((stream->offset - stream->channel_start_offset) % data->config.interleave_skip == 0)) {
stream->offset += data->config.interleave_skip * (data->config.subsong_skip - 1); stream->offset += data->config.interleave_skip * (data->config.subsong_skip - 1);
} }

View File

@ -17,7 +17,7 @@ static const char* extension_list[] = {
"aaap", "aaap",
"aax", "aax",
//"ac3", //FFmpeg, not parsed //common? //"ac3", //FFmpeg, not parsed //common?
"ace", //fake, for tri-Ace's formats "ace", //fake, for tri-Ace's formats (to be removed)
"acm", "acm",
"adm", "adm",
"adp", "adp",
@ -33,7 +33,7 @@ static const char* extension_list[] = {
"aix", "aix",
"akb", "akb",
"al2", "al2",
"amts", //fake extension (to be removed) "amts", //fake extension/header id for .stm (to be removed)
"ao", //txth/reserved [Cloudphobia (PC)] "ao", //txth/reserved [Cloudphobia (PC)]
"as4", "as4",
"asd", "asd",
@ -138,7 +138,7 @@ static const char* extension_list[] = {
"iab", "iab",
"iadp", "iadp",
"idsp", "idsp",
"idvi", //fake extension (to be removed) "idvi", //fake extension for .pcm (to be removed)
"ikm", "ikm",
"ild", "ild",
"int", "int",
@ -153,9 +153,10 @@ static const char* extension_list[] = {
"jstm", "jstm",
"kces", "kces",
"kcey", //fake extension (to be removed) "kcey", //fake extension/header id (to be removed)
"khv", "khv",
"kovs", "km9",
"kovs", //.kvs header id
"kraw", "kraw",
"ktss", "ktss",
"kvs", "kvs",
@ -206,7 +207,7 @@ static const char* extension_list[] = {
"ndp", "ndp",
"ngca", "ngca",
"nps", "nps",
"npsf", //fake extension (to be removed) "npsf", //fake extension/header id for .nps (to be removed)
"nus3bank", "nus3bank",
"nwa", "nwa",
@ -226,7 +227,7 @@ static const char* extension_list[] = {
"pnb", "pnb",
"pona", "pona",
"pos", "pos",
"ps2stm", //fake extension (to be removed) "ps2stm", //fake extension for .stm (to be removed)
"psh", "psh",
"psnd", "psnd",
"psw", "psw",
@ -282,6 +283,7 @@ static const char* extension_list[] = {
"snd", "snd",
"snds", "snds",
"sng", "sng",
"sngw",
"snr", "snr",
"sns", "sns",
"snu", "snu",
@ -574,6 +576,7 @@ static const layout_info layout_info_list[] = {
{layout_blocked_awc, "blocked (AWC)"}, {layout_blocked_awc, "blocked (AWC)"},
{layout_blocked_vgs, "blocked (VGS)"}, {layout_blocked_vgs, "blocked (VGS)"},
{layout_blocked_vawx, "blocked (VAWX)"}, {layout_blocked_vawx, "blocked (VAWX)"},
{layout_blocked_xvag_subsong, "blocked (XVAG subsong)"},
#ifdef VGM_USE_VORBIS #ifdef VGM_USE_VORBIS
{layout_ogg_vorbis, "Ogg"}, {layout_ogg_vorbis, "Ogg"},
#endif #endif
@ -933,16 +936,17 @@ static const meta_info meta_info_list[] = {
{meta_NGC_VID1, "Neversoft VID1 header"}, {meta_NGC_VID1, "Neversoft VID1 header"},
{meta_PC_FLX, "Ultima IX .FLX header"}, {meta_PC_FLX, "Ultima IX .FLX header"},
{meta_MOGG, "Harmonix Music Systems MOGG Vorbis"}, {meta_MOGG, "Harmonix Music Systems MOGG Vorbis"},
#ifdef VGM_USE_VORBIS
{meta_OGG_VORBIS, "Ogg Vorbis"}, {meta_OGG_VORBIS, "Ogg Vorbis"},
{meta_OGG_SLI, "Ogg Vorbis with .sli (start,length) for looping"}, {meta_OGG_SLI, "Ogg Vorbis with .sli (start,length) for looping"},
{meta_OGG_SLI2, "Ogg Vorbis with .sli (from,to) for looping"}, {meta_OGG_SLI2, "Ogg Vorbis with .sli (from,to) for looping"},
{meta_OGG_SFL, "Ogg Vorbis with SFPL for looping"}, {meta_OGG_SFL, "Ogg Vorbis with SFPL for looping"},
{meta_OGG_UM3, "Ogg Vorbis, Ultramarine3 'encryption'"}, {meta_OGG_UM3, "Ogg Vorbis (Ultramarine3)"},
{meta_OGG_KOVS, "Ogg Vorbis, KOVS header"}, {meta_OGG_KOVS, "Ogg Vorbis (KOVS header)"},
{meta_OGG_PSYCH, "Ogg Vorbis, Psychic Software obfuscation"}, {meta_OGG_PSYCHIC, "Ogg Vorbis (Psychic Software)"},
#endif {meta_OGG_SNGW, "Ogg Vorbis (Capcom)"},
{meta_OGG_ISD, "Ogg Vorbis (ISD)"},
{meta_KMA9, "Koei Tecmo KMA9 header"},
#ifdef VGM_USE_MP4V2 #ifdef VGM_USE_MP4V2
{meta_MP4, "AAC header"}, {meta_MP4, "AAC header"},
#endif #endif

View File

@ -164,6 +164,9 @@ void render_vgmstream_blocked(sample * buffer, int32_t sample_count, VGMSTREAM *
case layout_blocked_vawx: case layout_blocked_vawx:
block_update_vawx(vgmstream->next_block_offset,vgmstream); block_update_vawx(vgmstream->next_block_offset,vgmstream);
break; break;
case layout_blocked_xvag_subsong:
block_update_xvag_subsong(vgmstream->next_block_offset,vgmstream);
break;
default: default:
break; break;
} }

19
src/layout/blocked_xvag.c Normal file
View File

@ -0,0 +1,19 @@
#include "layout.h"
#include "../coding/coding.h"
#include "../vgmstream.h"
/* XVAG with subsongs layers, interleaves chunks of each subsong (a hack to support them) */
void block_update_xvag_subsong(off_t block_offset, VGMSTREAM * vgmstream) {
int i;
size_t channel_size = 0x10;
/* set offsets */
for (i = 0; i < vgmstream->channels; i++) {
vgmstream->ch[i].offset = block_offset + channel_size*i;
}
//vgmstream->current_block_size = ; /* fixed */
vgmstream->current_block_offset = block_offset;
vgmstream->next_block_offset = block_offset + vgmstream->full_block_size;
}

View File

@ -66,6 +66,7 @@ void block_update_ea_sns(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_awc(off_t block_offset, VGMSTREAM * vgmstream); void block_update_awc(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_vgs(off_t block_offset, VGMSTREAM * vgmstream); void block_update_vgs(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_vawx(off_t block_offset, VGMSTREAM * vgmstream); void block_update_vawx(off_t block_offset, VGMSTREAM * vgmstream);
void block_update_xvag_subsong(off_t block_offset, VGMSTREAM * vgmstream);
/* other layouts */ /* other layouts */
void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream); void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);

View File

@ -472,6 +472,10 @@
RelativePath=".\meta\ivb.c" RelativePath=".\meta\ivb.c"
> >
</File> </File>
<File
RelativePath=".\meta\kma9.c"
>
</File>
<File <File
RelativePath=".\meta\kraw.c" RelativePath=".\meta\kraw.c"
> >
@ -679,7 +683,7 @@
> >
</File> </File>
<File <File
RelativePath=".\meta\ogg_vorbis_file.c" RelativePath=".\meta\ogg_vorbis.c"
> >
</File> </File>
<File <File
@ -1669,6 +1673,10 @@
<File <File
RelativePath=".\layout\blocked_vgs.c" RelativePath=".\layout\blocked_vgs.c"
> >
</File>
<File
RelativePath=".\layout\blocked_xvag.c"
>
</File> </File>
<File <File
RelativePath=".\layout\caf_blocked.c" RelativePath=".\layout\caf_blocked.c"

View File

@ -226,6 +226,7 @@
<ClCompile Include="meta\ish_isd.c" /> <ClCompile Include="meta\ish_isd.c" />
<ClCompile Include="meta\ivaud.c" /> <ClCompile Include="meta\ivaud.c" />
<ClCompile Include="meta\ivb.c" /> <ClCompile Include="meta\ivb.c" />
<ClCompile Include="meta\kma9.c" />
<ClCompile Include="meta\kraw.c" /> <ClCompile Include="meta\kraw.c" />
<ClCompile Include="meta\maxis_xa.c" /> <ClCompile Include="meta\maxis_xa.c" />
<ClCompile Include="meta\mc3.c" /> <ClCompile Include="meta\mc3.c" />
@ -270,7 +271,7 @@
<ClCompile Include="meta\ngc_vid1.c" /> <ClCompile Include="meta\ngc_vid1.c" />
<ClCompile Include="meta\nub_xma.c" /> <ClCompile Include="meta\nub_xma.c" />
<ClCompile Include="meta\nwa.c" /> <ClCompile Include="meta\nwa.c" />
<ClCompile Include="meta\ogg_vorbis_file.c" /> <ClCompile Include="meta\ogg_vorbis.c" />
<ClCompile Include="meta\ogl.c" /> <ClCompile Include="meta\ogl.c" />
<ClCompile Include="meta\omu.c" /> <ClCompile Include="meta\omu.c" />
<ClCompile Include="meta\otm.c" /> <ClCompile Include="meta\otm.c" />
@ -464,6 +465,7 @@
<ClCompile Include="layout\blocked_ea_1snh.c" /> <ClCompile Include="layout\blocked_ea_1snh.c" />
<ClCompile Include="layout\blocked_vgs.c" /> <ClCompile Include="layout\blocked_vgs.c" />
<ClCompile Include="layout\blocked_vawx.c" /> <ClCompile Include="layout\blocked_vawx.c" />
<ClCompile Include="layout\blocked_xvag.c" />
<ClCompile Include="layout\caf_blocked.c" /> <ClCompile Include="layout\caf_blocked.c" />
<ClCompile Include="layout\blocked_dec.c" /> <ClCompile Include="layout\blocked_dec.c" />
<ClCompile Include="layout\blocked_ea_schl.c" /> <ClCompile Include="layout\blocked_ea_schl.c" />

View File

@ -283,6 +283,9 @@
<ClCompile Include="meta\ivb.c"> <ClCompile Include="meta\ivb.c">
<Filter>meta\Source Files</Filter> <Filter>meta\Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="meta\kma9.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\kraw.c"> <ClCompile Include="meta\kraw.c">
<Filter>meta\Source Files</Filter> <Filter>meta\Source Files</Filter>
</ClCompile> </ClCompile>
@ -400,7 +403,7 @@
<ClCompile Include="meta\nwa.c"> <ClCompile Include="meta\nwa.c">
<Filter>meta\Source Files</Filter> <Filter>meta\Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="meta\ogg_vorbis_file.c"> <ClCompile Include="meta\ogg_vorbis.c">
<Filter>meta\Source Files</Filter> <Filter>meta\Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="meta\ogl.c"> <ClCompile Include="meta\ogl.c">
@ -973,6 +976,9 @@
<ClCompile Include="layout\blocked_vawx.c"> <ClCompile Include="layout\blocked_vawx.c">
<Filter>layout\Source Files</Filter> <Filter>layout\Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="layout\blocked_xvag.c">
<Filter>layout\Source Files</Filter>
</ClCompile>
<ClCompile Include="layout\caf_blocked.c"> <ClCompile Include="layout\caf_blocked.c">
<Filter>layout\Source Files</Filter> <Filter>layout\Source Files</Filter>
</ClCompile> </ClCompile>

78
src/meta/kma9.c Normal file
View File

@ -0,0 +1,78 @@
#include "meta.h"
#include "../coding/coding.h"
/* KMA9 - Koei Tecmo's custom ATRAC9 [Nobunaga no Yabou - Souzou (Vita)] */
VGMSTREAM * init_vgmstream_kma9(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
int loop_flag, channel_count;
int total_subsongs = 0, target_subsong = streamFile->stream_index;
/* check extension */
if ( !check_extensions(streamFile,"km9") )
goto fail;
/* check header */
if (read_32bitBE(0x00,streamFile) != 0x4B4D4139) /* "KMA9" */
goto fail;
start_offset = read_32bitLE(0x04,streamFile);
channel_count = read_16bitLE(0x32,streamFile);
loop_flag = (read_32bitLE(0x28,streamFile) != 0);
total_subsongs = read_32bitLE(0x08,streamFile);
if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
/* 0x0c: unknown */
/* 0x14: data size of each subsong */
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = read_32bitLE(0x34,streamFile);
vgmstream->num_samples = read_32bitLE(0x18,streamFile); /* without skip_samples? */
vgmstream->loop_start_sample = read_32bitLE(0x24,streamFile); /* with skip_samples? */
vgmstream->loop_end_sample = vgmstream->num_samples; /* 0x28 looks like end samples but isn't, no idea */
vgmstream->num_streams = total_subsongs;
vgmstream->meta_type = meta_KMA9;
#ifdef VGM_USE_ATRAC9
{
atrac9_config cfg = {0};
cfg.type = ATRAC9_KMA9;
cfg.channels = vgmstream->channels;
cfg.config_data = read_32bitBE(0x5c,streamFile);
cfg.encoder_delay = read_32bitLE(0x20,streamFile);
cfg.interleave_skip = read_32bitLE(0x10,streamFile); /* 1 superframe */
cfg.subsong_skip = total_subsongs;
start_offset += (target_subsong-1) * cfg.interleave_skip * (cfg.subsong_skip-1);
vgmstream->codec_data = init_atrac9(&cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_ATRAC9;
vgmstream->layout_type = layout_none;
if (loop_flag) { /* seems correct but must double check */
vgmstream->loop_start_sample -= cfg.encoder_delay;
//vgmstream->loop_end_sample -= cfg.encoder_delay;
}
}
#else
goto fail;
#endif
/* open the file for reading */
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -99,11 +99,14 @@ typedef struct {
meta_t meta_type; meta_t meta_type;
layout_t layout_type; layout_t layout_type;
/* XOR setup (SCD) */ off_t stream_size;
int decryption_enabled; int total_subsongs;
void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read);
/* decryption setup */
void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource);
uint8_t scd_xor; uint8_t scd_xor;
off_t scd_xor_length; off_t scd_xor_length;
uint32_t sngw_xor;
} vgm_vorbis_info_t; } vgm_vorbis_info_t;
@ -690,4 +693,6 @@ VGMSTREAM * init_vgmstream_flx(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_mogg(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_mogg(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_kma9(STREAMFILE * streamFile);
#endif /*_META_H*/ #endif /*_META_H*/

453
src/meta/ogg_vorbis.c Normal file
View File

@ -0,0 +1,453 @@
#include "../vgmstream.h"
#ifdef VGM_USE_VORBIS
#include <stdio.h>
#include <string.h>
#include "meta.h"
#include <vorbis/vorbisfile.h>
#define OGG_DEFAULT_BITSTREAM 0
static size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void * datasource) {
ogg_vorbis_streamfile * const ov_streamfile = datasource;
size_t bytes_read, items_read;
off_t real_offset = ov_streamfile->start + ov_streamfile->offset;
size_t max_bytes = size * nmemb;
/* clamp for virtual filesize */
if (max_bytes > ov_streamfile->size - ov_streamfile->offset)
max_bytes = ov_streamfile->size - ov_streamfile->offset;
bytes_read = read_streamfile(ptr, real_offset, max_bytes, ov_streamfile->streamfile);
items_read = bytes_read / size;
/* may be encrypted */
if (ov_streamfile->decryption_callback) {
ov_streamfile->decryption_callback(ptr, size, items_read, ov_streamfile);
}
ov_streamfile->offset += items_read * size;
return items_read;
}
static int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) {
ogg_vorbis_streamfile * const ov_streamfile = datasource;
ogg_int64_t base_offset, new_offset;
switch (whence) {
case SEEK_SET:
base_offset = 0;
break;
case SEEK_CUR:
base_offset = ov_streamfile->offset;
break;
case SEEK_END:
base_offset = ov_streamfile->size;
break;
default:
return -1;
break;
}
new_offset = base_offset + offset;
if (new_offset < 0 || new_offset > ov_streamfile->size) {
return -1; /* *must* return -1 if stream is unseekable */
} else {
ov_streamfile->offset = new_offset;
return 0;
}
}
static long ov_tell_func(void * datasource) {
ogg_vorbis_streamfile * const ov_streamfile = datasource;
return ov_streamfile->offset;
}
static int ov_close_func(void * datasource) {
/* needed as setting ov_close_func in ov_callbacks to NULL doesn't seem to work
* (closing the streamfile is done in free_ogg_vorbis) */
return 0;
}
static void um3_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
size_t bytes_read = size*nmemb;
ogg_vorbis_streamfile * const ov_streamfile = datasource;
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;
}
}
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);
}
}
}
}
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];
}
}
/* 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;
int is_sngw = 0;
int is_isd = 0;
/* check extension */
if (check_extensions(streamFile,"ogg,logg")) { /* .ogg: standard/psychic, .logg: renamed for plugins */
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;
} else if (check_extensions(streamFile,"sngw")) { /* .sngw: Devil May Cry 4 SE (PC), Biohazard 6 (PC) */
is_sngw = 1;
} else if (check_extensions(streamFile,"isd")) { /* .isd: Azure Striker Gunvolt (PC) */
is_isd = 1;
} 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;
}
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;
}
/* 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;
}
}
/* 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)
}
if (is_um3) {
inf.meta_type = meta_OGG_UM3;
} else if (is_kovs) {
inf.meta_type = meta_OGG_KOVS;
} else if (is_psychic) {
inf.meta_type = meta_OGG_PSYCHIC;
} else if (is_sngw) {
inf.meta_type = meta_OGG_SNGW;
} else if (is_isd) {
inf.meta_type = meta_OGG_ISD;
} else {
inf.meta_type = meta_OGG_VORBIS;
}
inf.layout_type = layout_ogg_vorbis;
return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);
fail:
return NULL;
}
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) {
VGMSTREAM * vgmstream = NULL;
ogg_vorbis_codec_data * data = NULL;
OggVorbis_File *ovf = NULL;
vorbis_info *vi;
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 = 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;
ogg_vorbis_streamfile temp_streamfile;
temp_streamfile.streamfile = streamFile;
temp_streamfile.start = start;
temp_streamfile.offset = 0;
temp_streamfile.size = vgm_inf->stream_size ?
vgm_inf->stream_size :
get_streamfile_size(temp_streamfile.streamfile) - start;
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;
temp_streamfile.sngw_xor = vgm_inf->sngw_xor;
/* 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;
data->ov_streamfile.start = start;
data->ov_streamfile.offset = 0;
data->ov_streamfile.size = vgm_inf->stream_size ?
vgm_inf->stream_size :
get_streamfile_size(data->ov_streamfile.streamfile) - start;
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;
data->ov_streamfile.sngw_xor = vgm_inf->sngw_xor;
/* 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;
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);
}
else if (strstr(user_comment,"LOOPLENGTH=")==user_comment) {/* (LOOPSTART pair) */
loop_length = atol(strrchr(user_comment,'=')+1);
loop_length_found = 1;
}
else if (strstr(user_comment,"title=-lps")==user_comment) { /* Memories Off #5 (PC) */
loop_start = atol(user_comment+10);
loop_flag = (loop_start >= 0);
}
else if (strstr(user_comment,"album=-lpe")==user_comment) { /* (title=-lps pair) */
loop_end = atol(user_comment+10);
loop_flag = 1;
loop_end_found = 1;
}
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;
}
}
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;
}
}
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;
}
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;
}
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;
}
}
}
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(vi->channels,loop_flag);
if (!vgmstream) goto fail;
vgmstream->codec_data = data; /* store our fun extra datas */
vgmstream->channels = vi->channels;
vgmstream->sample_rate = vi->rate;
vgmstream->num_streams = vgm_inf->total_subsongs;
vgmstream->num_samples = ov_pcm_total(ovf,-1); /* let libvorbisfile find total samples */
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;
fail:
/* clean up anything we may have opened */
if (data) {
if (ovf)
ov_clear(&data->ogg_vorbis_file);//same as ovf
if (data->ov_streamfile.streamfile)
close_streamfile(data->ov_streamfile.streamfile);
free(data);
}
if (vgmstream) {
vgmstream->codec_data = NULL;
close_vgmstream(vgmstream);
}
return NULL;
}
#endif

View File

@ -1,461 +0,0 @@
#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;
/* may be encrypted */
if (ov_streamfile->decryption_enabled) {
ov_streamfile->decryption_callback(ptr, size, nmemb, ov_streamfile, bytes_read);
}
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 (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 {
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_OGG_UM3;
} else if (kovs_ogg) {
inf.meta_type = meta_OGG_KOVS;
} else if (psych_ogg) {
inf.meta_type = meta_OGG_PSYCH;
} 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->decryption_enabled) {
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.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? */
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.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 */
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],"LOOPDEFS=")==
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

View File

@ -4,20 +4,19 @@
#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 console games (FF XIII, XIV) */ /* SCD - Square-Enix games (FF XIII, XIV) */
VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT]; char filename[PATH_LIMIT];
off_t start_offset, tables_offset, headers_offset, meta_offset, post_meta_offset; off_t start_offset, tables_offset, meta_offset, post_meta_offset;
int headers_entries; int32_t stream_size, subheader_size, loop_start, loop_end;
int32_t stream_size, loop_start, loop_end;
int target_stream = streamFile->stream_index; int total_subsongs, target_subsong = streamFile->stream_index;
int loop_flag = 0, channel_count, codec_id, sample_rate; int loop_flag = 0, channel_count, codec, sample_rate;
int aux_chunk_count; int aux_chunk_count;
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
@ -25,32 +24,34 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
/* check extension, case insensitive */ /* check extension, case insensitive */
if ( !check_extensions(streamFile, "scd") ) goto fail; if ( !check_extensions(streamFile, "scd") )
goto fail;
streamFile->get_name(streamFile,filename,sizeof(filename)); streamFile->get_name(streamFile,filename,sizeof(filename));
/* SEDB */ /** main header **/
if (read_32bitBE(0x00,streamFile) != 0x53454442) goto fail; if (read_32bitBE(0x00,streamFile) != 0x53454442 && /* "SEDB" */
/* SSCF */ read_32bitBE(0x04,streamFile) != 0x53534346) /* "SSCF" */
if (read_32bitBE(0x04,streamFile) != 0x53534346) goto fail; goto fail;
/** main header section **/
if (read_32bitBE(0x08,streamFile) == 2 || /* version 2 BE, as seen in FFXIII demo for PS3 */ if (read_32bitBE(0x08,streamFile) == 2 || /* version 2 BE, as seen in FFXIII demo for PS3 */
read_32bitBE(0x08,streamFile) == 3) { /* version 3 BE, as seen in FFXIII for PS3 */ read_32bitBE(0x08,streamFile) == 3) { /* version 3 BE, as seen in FFXIII for PS3 */
//size_offset = 0x14;
read_32bit = read_32bitBE; read_32bit = read_32bitBE;
read_16bit = read_16bitBE; read_16bit = read_16bitBE;
//size_offset = 0x14; }
} else if (read_32bitLE(0x08,streamFile) == 3 || /* version 2/3 LE, as seen in FFXIV for PC (and others?) */ else if (read_32bitLE(0x08,streamFile) == 2 || /* version 2/3 LE, as seen in FFXIV for PC (and others) */
read_32bitLE(0x08,streamFile) == 2) { read_32bitLE(0x08,streamFile) == 3) {
//size_offset = 0x10;
read_32bit = read_32bitLE; read_32bit = read_32bitLE;
read_16bit = read_16bitLE; read_16bit = read_16bitLE;
//size_offset = 0x10; }
} else goto fail; else {
goto fail;
}
/* 0x0c: probably 0=LE, 1=BE */ /* 0x0c: probably 0=LE, 1=BE */
/* 0x0d: unk (always 0x04) */ /* 0x0d: unknown (always 0x04) */
tables_offset = read_16bit(0xe,streamFile); tables_offset = read_16bit(0x0e,streamFile); /* usually 0x30 or 0x20 */
#if 0 #if 0
/* never mind, FFXIII music_68tak.ps3.scd is 0x80 shorter */ /* never mind, FFXIII music_68tak.ps3.scd is 0x80 shorter */
@ -59,43 +60,66 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
goto fail; goto fail;
#endif #endif
/** offset tables **/
/* 0x00: table1_unknown entries */
/* 0x02: table2_unknown entries */
/* 0x04: table_headers entries */
/* 0x06: unknown (varies) */
/* 0x08: table1_unknown start offset */
/* 0x0c: table_headers start offset */
/* 0x10: table2_unknown start offset */
/* 0x14: unknown (0x0) */
/* 0x18: unknown offset */
/* 0x1c: unknown (0x0) */
headers_entries = read_16bit(tables_offset+0x04,streamFile);
if (target_stream == 0) target_stream = 1; /* auto: default to 1 */
if (target_stream < 0 || target_stream > headers_entries || headers_entries < 1) goto fail;
headers_offset = read_32bit(tables_offset+0x0c,streamFile); /** offset tables **/
/* 0x00(2): table1/4 (unknown) entries */
/* 0x02(2): table2 (unknown) entries */
/* 0x04(2): table3 (headers) entries */
/* 0x06(2): unknown, varies even for clone files */
/** header table entries (each is an uint32_t offset to stream header) **/ /* (implicit: table1 starts at 0x20) */
meta_offset = read_32bit(headers_offset + (target_stream-1)*4,streamFile); /* 0x08: table2 (unknown) start offset */
/* 0x0c: table3 (headers) start offset */
/* 0x10: table4 (unknown) start offset */
/* 0x14: always null? */
/* 0x18: table5? (unknown) start offset? */
/* 0x1c: unknown, often null */
/* each table entry is an uint32_t offset */
/* if a table isn't present entries is 0 and offset points to next table */
/* find meta_offset in table3 (headers) and total subsongs */
{
int i;
int headers_entries = read_16bit(tables_offset+0x04,streamFile);
off_t headers_offset = read_32bit(tables_offset+0x0c,streamFile);
if (target_subsong == 0) target_subsong = 1;
total_subsongs = 0;
meta_offset = 0;
/* manually find subsongs as entries can be dummy (ex. sfx banks in FF XIV or FF Type-0) */
for (i = 0; i < headers_entries; i++) {
off_t header_offset = read_32bit(headers_offset + i*0x04,streamFile);
if (read_32bit(header_offset+0x0c,streamFile) == -1)
continue; /* codec -1 when dummy */
total_subsongs++;
if (!meta_offset && total_subsongs == target_subsong)
meta_offset = header_offset;
}
if (meta_offset == 0) goto fail;
/* SCD can contain 0 entries too */
}
/** stream header **/ /** stream header **/
stream_size = read_32bit(meta_offset+0x00,streamFile); stream_size = read_32bit(meta_offset+0x00,streamFile);
channel_count = read_32bit(meta_offset+0x04,streamFile); channel_count = read_32bit(meta_offset+0x04,streamFile);
sample_rate = read_32bit(meta_offset+0x08,streamFile); sample_rate = read_32bit(meta_offset+0x08,streamFile);
codec_id = read_32bit(meta_offset+0x0c,streamFile); codec = read_32bit(meta_offset+0x0c,streamFile);
loop_start = read_32bit(meta_offset+0x10,streamFile); loop_start = read_32bit(meta_offset+0x10,streamFile);
loop_end = read_32bit(meta_offset+0x14,streamFile); loop_end = read_32bit(meta_offset+0x14,streamFile);
loop_flag = (loop_end > 0); subheader_size = read_32bit(meta_offset+0x18,streamFile);
post_meta_offset = meta_offset + 0x20;
start_offset = post_meta_offset + read_32bit(meta_offset+0x18,streamFile);
aux_chunk_count = read_32bit(meta_offset+0x1c,streamFile); aux_chunk_count = read_32bit(meta_offset+0x1c,streamFile);
/* 0x01e(e): unknown, seen in some FF XIV sfx (IMA) */ /* 0x01e(2): unknown, seen in some FF XIV sfx (MSADPCM) */
loop_flag = (loop_end > 0);
post_meta_offset = meta_offset + 0x20;
start_offset = post_meta_offset + subheader_size;
/* only "MARK" chunk is known (some FF XIV PS3 have "STBL" but it's not counted) */ /* only "MARK" chunk is known (some FF XIV PS3 have "STBL" but it's not counted) */
if (aux_chunk_count > 1 && aux_chunk_count < 0xFFFF) { /* some FF XIV Heavensward IMA sfx has 0x01000000 */ if (aux_chunk_count > 1 && aux_chunk_count < 0xFFFF) { /* some FF XIV Heavensward IMA sfx have 0x01000000 */
VGM_LOG("SCD: unknown aux chunk count %i\n", aux_chunk_count); VGM_LOG("SCD: unknown aux chunk count %i\n", aux_chunk_count);
goto fail; goto fail;
} }
@ -107,73 +131,52 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
#ifdef VGM_USE_VORBIS #ifdef VGM_USE_VORBIS
/* special case using init_vgmstream_ogg_vorbis with callbacks */ /* special case using init_vgmstream_ogg_vorbis */
if (codec_id == 0x06) { if (codec == 0x06) {
VGMSTREAM * result = NULL; uint8_t ogg_version, ogg_byte;
uint32_t seek_table_size, vorb_header_size;
uint8_t xor_version, xor_byte;
vgm_vorbis_info_t inf = {0}; vgm_vorbis_info_t inf = {0};
inf.loop_start = loop_start;
inf.loop_end = loop_end;
inf.loop_flag = loop_flag;
inf.loop_end_found = loop_flag;
inf.loop_length_found = 0;
inf.layout_type = layout_ogg_vorbis; inf.layout_type = layout_ogg_vorbis;
inf.meta_type = meta_SQEX_SCD; inf.meta_type = meta_SQEX_SCD;
inf.total_subsongs = total_subsongs;
/* loop values are in bytes, let init_vgmstream_ogg_vorbis find loop comments instead */
/* the following could be simplified but it's not clear what field signals that seek table exists ogg_version = read_8bit(post_meta_offset + 0x00, streamFile);
* (seems that encrypted = always seek table, but maybe post_meta_offset+0x01 = 0x20) */ /* 0x01(1): 0x20 in v2/3, this ogg miniheader size? */
ogg_byte = read_8bit(post_meta_offset + 0x02, streamFile);
/* 0x03(1): ? in v3 */
/* try regular Ogg with default values */ if (ogg_version == 0) { /* 0x10? header, then custom Vorbis header before regular Ogg (FF XIV PC v1) */
{ inf.stream_size = stream_size;
result = init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);
if (result != NULL)
return result;
} }
else { /* 0x20 header, then seek table */
size_t seek_table_size = read_32bit(post_meta_offset+0x10, streamFile);
size_t vorb_header_size = read_32bit(post_meta_offset+0x14, streamFile);
/* 0x18(4): ? (can be 0) */
/* skip seek table and try regular Ogg again */ if ((post_meta_offset-meta_offset) + seek_table_size + vorb_header_size != subheader_size)
{ goto fail;
seek_table_size = read_32bit(post_meta_offset+0x10, streamFile);
vorb_header_size = read_32bit(post_meta_offset+0x14, streamFile);
if ((post_meta_offset-meta_offset) + seek_table_size + vorb_header_size != read_32bit(meta_offset+0x18, streamFile)) {
return NULL;
}
start_offset = post_meta_offset + 0x20 + seek_table_size; inf.stream_size = vorb_header_size + stream_size;
start_offset = post_meta_offset + 0x20 + seek_table_size; /* subheader_size skips vorb_header */
result = init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf); if (ogg_version == 2) { /* header is XOR'ed using byte (FF XIV PC) */
if (result != NULL) inf.decryption_callback = scd_ogg_v2_decryption_callback;
return result; inf.scd_xor = read_8bit(post_meta_offset + 0x02, streamFile);
}
/* try encrypted Ogg (with seek table already skipped) */
{
xor_version = read_8bit(post_meta_offset + 0x00, streamFile);
xor_byte = read_8bit(post_meta_offset + 0x02, streamFile);
if (xor_byte == 0)
return NULL; /* not actually encrypted, happens but should be handled above */
if (xor_version == 2) { /* header is XOR'ed using byte */
inf.decryption_enabled = 1;
inf.decryption_callback = scd_ogg_decrypt_v2_callback;
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 (ogg_version == 3) { /* file is XOR'ed using table (FF XIV Heavensward PC) */
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; /* ogg_byte not used? */
inf.scd_xor = stream_size & 0xFF; /* xor_byte is not used? (also there is data at +0x03) */ inf.scd_xor_length = vorb_header_size + stream_size;
inf.scd_xor_length = stream_size;
} }
else { else {
VGM_LOG("SCD: unknown encryption 0x%x\n", xor_version); VGM_LOG("SCD: unknown ogg_version 0x%x\n", ogg_version);
return NULL;
} }
/* hope this works */
return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);
} }
/* actual Ogg init */
return init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);
} }
#endif #endif
@ -185,18 +188,31 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
/* fill in the vital statistics */ /* fill in the vital statistics */
vgmstream->channels = channel_count; vgmstream->channels = channel_count;
vgmstream->sample_rate = sample_rate; vgmstream->sample_rate = sample_rate;
vgmstream->num_streams = headers_entries; vgmstream->num_streams = total_subsongs;
vgmstream->meta_type = meta_SQEX_SCD; vgmstream->meta_type = meta_SQEX_SCD;
switch (codec_id) { switch (codec) {
case 0x01: /* PCM */ case 0x01: /* PCM */
vgmstream->coding_type = coding_PCM16_int; vgmstream->coding_type = coding_PCM16LE;
vgmstream->layout_type = layout_none; vgmstream->layout_type = layout_interleave;
vgmstream->num_samples = stream_size / 2 / channel_count; vgmstream->interleave_block_size = 0x02;
vgmstream->num_samples = pcm_bytes_to_samples(stream_size, channel_count, 16);
if (loop_flag) { if (loop_flag) {
vgmstream->loop_start_sample = loop_start / 2 / channel_count; vgmstream->loop_start_sample = pcm_bytes_to_samples(loop_start, channel_count, 16);
vgmstream->loop_end_sample = loop_end / 2 / channel_count; vgmstream->loop_end_sample = pcm_bytes_to_samples(loop_end, channel_count, 16);
}
break;
case 0x03: /* PS-ADPCM [Final Fantasy Type-0] */
vgmstream->coding_type = coding_PSX;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x10;
vgmstream->num_samples = ps_bytes_to_samples(stream_size, channel_count);
if (loop_flag) {
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channel_count);
vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channel_count);
} }
break; break;
@ -234,6 +250,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
vgmstream->coding_type = coding_MSADPCM; vgmstream->coding_type = coding_MSADPCM;
vgmstream->layout_type = layout_none; vgmstream->layout_type = layout_none;
vgmstream->interleave_block_size = read_16bit(post_meta_offset+0x0c,streamFile); vgmstream->interleave_block_size = read_16bit(post_meta_offset+0x0c,streamFile);
/* in post_meta_offset is a WAVEFORMATEX (including coefs and all) */
vgmstream->num_samples = msadpcm_bytes_to_samples(stream_size, vgmstream->interleave_block_size, vgmstream->channels); vgmstream->num_samples = msadpcm_bytes_to_samples(stream_size, vgmstream->interleave_block_size, vgmstream->channels);
if (loop_flag) { if (loop_flag) {
@ -324,7 +341,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
break; break;
} }
case 0x0E: { /* ATRAC3plus [Lord of Arcana (PSP)] */ case 0x0E: { /* ATRAC3/ATRAC3plus [Lord of Arcana (PSP), Final Fantasy Type-0] */
ffmpeg_codec_data *ffmpeg_data = NULL; ffmpeg_codec_data *ffmpeg_data = NULL;
/* full RIFF header at start_offset/post_meta_offset (same) */ /* full RIFF header at start_offset/post_meta_offset (same) */
@ -357,8 +374,9 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
} }
#endif #endif
case -1: /* used for dummy entries */
default: default:
VGM_LOG("SCD: unknown codec_id 0x%x\n", codec_id); VGM_LOG("SCD: unknown codec 0x%x\n", codec);
goto fail; goto fail;
} }
@ -375,9 +393,14 @@ 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;
/* no encryption, sometimes happens */
if (ov_streamfile->scd_xor == 0x00)
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 (ov_streamfile->offset < ov_streamfile->scd_xor_length) {
int i, num_crypt; int i, num_crypt;
@ -392,9 +415,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
@ -412,23 +435,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;
} }
} }
} }

View File

@ -1,6 +1,7 @@
#include "meta.h" #include "meta.h"
#include "../coding/coding.h" #include "../coding/coding.h"
#include "../util.h" #include "../layout/layout.h"
static int ps_adpcm_find_loop_offsets(STREAMFILE *streamFile, int channel_count, off_t start_offset, off_t * loop_start, off_t * loop_end); static int ps_adpcm_find_loop_offsets(STREAMFILE *streamFile, int channel_count, off_t start_offset, off_t * loop_start, off_t * loop_end);
@ -10,10 +11,10 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
int loop_flag = 0, channel_count, codec; int loop_flag = 0, channel_count, codec;
int big_endian; int big_endian;
int sample_rate, num_samples, multiplier, multistreams = 0; int sample_rate, num_samples, interleave_factor, multistreams = 0;
int total_subsongs = 0, target_subsong = streamFile->stream_index; int total_subsongs = 0, target_subsong = streamFile->stream_index;
off_t start_offset, loop_start, loop_end, chunk_offset; off_t start_offset, loop_start = 0, loop_end = 0, chunk_offset;
off_t first_offset = 0x20; off_t first_offset = 0x20;
size_t chunk_size; size_t chunk_size;
@ -37,7 +38,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
/* 0x08: flags? (&0x01=big endian, 0x02=?, 0x06=full RIFF AT9?) /* 0x08: flags? (&0x01=big endian, 0x02=?, 0x06=full RIFF AT9?)
* 0x09: flags2? (0x00/0x01/0x04, speaker mode?) * 0x09: flags2? (0x00/0x01/0x04, speaker mode?)
* 0x0a: always 0? * 0x0a: always 0?
* 0x0b: version-flag? (0x5f/0x60/0x61, last has extra data) */ * 0x0b: version-flag? (0x5f/0x60/0x61/0x62/etc) */
/* "fmat": base format (always first) */ /* "fmat": base format (always first) */
if (!find_chunk(streamFile, 0x666D6174,first_offset,0, &chunk_offset,&chunk_size, big_endian, 1)) /*"fmat"*/ if (!find_chunk(streamFile, 0x666D6174,first_offset,0, &chunk_offset,&chunk_size, big_endian, 1)) /*"fmat"*/
@ -48,14 +49,20 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
/* 0x0c: samples again? playable section? */ /* 0x0c: samples again? playable section? */
VGM_ASSERT(num_samples != read_32bit(chunk_offset+0x0c,streamFile), "XVAG: num_samples values don't match\n"); VGM_ASSERT(num_samples != read_32bit(chunk_offset+0x0c,streamFile), "XVAG: num_samples values don't match\n");
multiplier = read_32bit(chunk_offset+0x10,streamFile); /* 'interleave factor' */ interleave_factor = read_32bit(chunk_offset+0x10,streamFile);
sample_rate = read_32bit(chunk_offset+0x14,streamFile); sample_rate = read_32bit(chunk_offset+0x14,streamFile);
/* 0x18: datasize */ /* 0x18: datasize */
/* extra data, seen in MPEG/ATRAC9 */ /* extra data, seen in versions 0x61+ */
if (chunk_size > 0x1c) { if (chunk_size > 0x1c) {
total_subsongs = read_32bit(chunk_offset+0x1c,streamFile); /* number of interleaved layers */ /* number of interleaved subsong layers */
multistreams = read_32bit(chunk_offset+0x20,streamFile); /* number of bitstreams per layer (for multistream Nch MPEG/ATRAC9) */ total_subsongs = read_32bit(chunk_offset+0x1c,streamFile);
/* number of interleaved bitstreams per layer (multistreams * channels_per_stream = channels) */
multistreams = read_32bit(chunk_offset+0x20,streamFile);
}
else {
total_subsongs = 1;
multistreams = 1;
} }
if (target_subsong == 0) target_subsong = 1; if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
@ -66,9 +73,9 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
/* "cues": cue/labels (rare) */ /* "cues": cue/labels (rare) */
/* "0000": end chunk before start_offset */ /* "0000": end chunk before start_offset */
/* some XVAG seem to do full loops, this should detect them as looping */ /* some XVAG seem to do full loops, this should detect them as looping (basically tests is last frame is empty) */
//todo remove, looping seems external and specified in Scream Tool's bank formats //todo remove, looping seems external and specified in Scream Tool's bank formats
if (codec == 0x06) { if (codec == 0x06 && total_subsongs == 1) {
loop_flag = ps_adpcm_find_loop_offsets(streamFile, channel_count, start_offset, &loop_start, &loop_end); loop_flag = ps_adpcm_find_loop_offsets(streamFile, channel_count, start_offset, &loop_start, &loop_end);
} }
@ -82,27 +89,41 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
vgmstream->meta_type = meta_XVAG; vgmstream->meta_type = meta_XVAG;
switch (codec) { switch (codec) {
//case 0x??: /* PCM? */
case 0x06: /* VAG (PS ADPCM): God of War III (PS3), Uncharted 1/2 (PS3), Ratchet and Clank Future (PS3) */ case 0x06: /* VAG (PS ADPCM): God of War III (PS3), Uncharted 1/2 (PS3), Ratchet and Clank Future (PS3) */
case 0x07: /* SVAG? (PS ADPCM with extended table): inFamous 1 (PS3) */ case 0x07: /* SVAG? (PS ADPCM with extended table): inFamous 1 (PS3) */
if (total_subsongs > 1 || multistreams > 1) goto fail; if (multistreams > 1 && multistreams != vgmstream->channels) goto fail;
if (multiplier > 1) goto fail; if (total_subsongs > 1 && multistreams > 1) goto fail;
if (total_subsongs > 1 && vgmstream->channels > 1) goto fail; /* unknown layout */
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x10;
vgmstream->coding_type = coding_PSX; vgmstream->coding_type = coding_PSX;
if (total_subsongs > 1) { /* God of War 3 (PS4) */
vgmstream->layout_type = layout_blocked_xvag_subsong;
vgmstream->interleave_block_size = 0x10;
vgmstream->full_block_size = 0x10 * interleave_factor * total_subsongs;
vgmstream->current_block_size = 0x10 * interleave_factor;
start_offset += 0x10 * interleave_factor * (target_subsong-1);
}
else {
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x10 * interleave_factor; /* usually 1, bigger in GoW3 PS4 */
}
if (loop_flag) { if (loop_flag) {
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, vgmstream->channels); vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, vgmstream->channels);
vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, vgmstream->channels); vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, vgmstream->channels);
} }
break; break;
#ifdef VGM_USE_MPEG #ifdef VGM_USE_MPEG
case 0x08: { /* MPEG: The Last of Us (PS3), Uncharted 3 (PS3), Medieval Moves (PS3) */ case 0x08: { /* MPEG: The Last of Us (PS3), Uncharted 3 (PS3), Medieval Moves (PS3) */
mpeg_custom_config cfg = {0}; mpeg_custom_config cfg = {0};
if (total_subsongs > 1 || (multistreams > 1 && multistreams == vgmstream->channels)) goto fail; /* often 2ch per MPEG and rarely 1ch (GoW3 PS4) */
if (multistreams > 1 && !(multistreams*1 == vgmstream->channels || multistreams*2 == vgmstream->channels)) goto fail;
if (total_subsongs > 1) goto fail;
//todo rare test file in The Last of Us PS4 uses 6ch with 1 2ch stream, surround MPEG/mp3pro?
/* "mpin": mpeg info */ /* "mpin": mpeg info */
/* 0x00/04: mpeg version/layer? other: unknown or repeats of "fmat" */ /* 0x00/04: mpeg version/layer? other: unknown or repeats of "fmat" */
@ -110,7 +131,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
goto fail; goto fail;
cfg.chunk_size = read_32bit(chunk_offset+0x1c,streamFile); /* fixed frame size */ cfg.chunk_size = read_32bit(chunk_offset+0x1c,streamFile); /* fixed frame size */
cfg.interleave = cfg.chunk_size * multiplier; cfg.interleave = cfg.chunk_size * interleave_factor;
vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_XVAG, &cfg); vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_XVAG, &cfg);
if (!vgmstream->codec_data) goto fail; if (!vgmstream->codec_data) goto fail;
@ -138,7 +159,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
} }
else if (total_subsongs > 1) { else if (total_subsongs > 1) {
/* interleaves 'multiplier' superframes per subsong (all share config_data) */ /* interleaves 'multiplier' superframes per subsong (all share config_data) */
cfg.interleave_skip = read_32bit(chunk_offset+0x00,streamFile) * multiplier; cfg.interleave_skip = read_32bit(chunk_offset+0x00,streamFile) * interleave_factor;
cfg.subsong_skip = total_subsongs; cfg.subsong_skip = total_subsongs;
/* start in subsong's first superframe */ /* start in subsong's first superframe */
start_offset += (target_subsong-1) * cfg.interleave_skip * (cfg.subsong_skip-1); start_offset += (target_subsong-1) * cfg.interleave_skip * (cfg.subsong_skip-1);
@ -158,6 +179,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
} }
#endif #endif
//case 0x??: /* PCM? */
default: default:
goto fail; goto fail;
} }
@ -167,6 +189,9 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail; goto fail;
if (vgmstream->layout_type == layout_blocked_xvag_subsong)
block_update_xvag_subsong(start_offset, vgmstream);
return vgmstream; return vgmstream;
fail: fail:

View File

@ -373,6 +373,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_ngc_vid1, init_vgmstream_ngc_vid1,
init_vgmstream_flx, init_vgmstream_flx,
init_vgmstream_mogg, init_vgmstream_mogg,
init_vgmstream_kma9,
init_vgmstream_txth, /* should go at the end (lower priority) */ init_vgmstream_txth, /* should go at the end (lower priority) */
#ifdef VGM_USE_FFMPEG #ifdef VGM_USE_FFMPEG
@ -973,6 +974,7 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre
case layout_blocked_awc: case layout_blocked_awc:
case layout_blocked_vgs: case layout_blocked_vgs:
case layout_blocked_vawx: case layout_blocked_vawx:
case layout_blocked_xvag_subsong:
render_vgmstream_blocked(buffer,sample_count,vgmstream); render_vgmstream_blocked(buffer,sample_count,vgmstream);
break; break;
case layout_acm: case layout_acm:

View File

@ -251,6 +251,7 @@ typedef enum {
layout_blocked_awc, /* Rockstar AWC */ layout_blocked_awc, /* Rockstar AWC */
layout_blocked_vgs, /* Guitar Hero II (PS2) */ layout_blocked_vgs, /* Guitar Hero II (PS2) */
layout_blocked_vawx, /* No More Heroes 6ch (PS3) */ layout_blocked_vawx, /* No More Heroes 6ch (PS3) */
layout_blocked_xvag_subsong, /* XVAG subsongs [God of War III (PS4)] */
/* otherwise odd */ /* otherwise odd */
layout_acm, /* libacm layout */ layout_acm, /* libacm layout */
@ -645,16 +646,17 @@ typedef enum {
meta_NGC_VID1, /* Neversoft .ogg (Gun GC) */ meta_NGC_VID1, /* Neversoft .ogg (Gun GC) */
meta_PC_FLX, /* Ultima IX PC */ meta_PC_FLX, /* Ultima IX PC */
meta_MOGG, /* Harmonix Music Systems MOGG Vorbis */ meta_MOGG, /* Harmonix Music Systems MOGG Vorbis */
#ifdef VGM_USE_VORBIS
meta_OGG_VORBIS, /* Ogg Vorbis */ meta_OGG_VORBIS, /* Ogg Vorbis */
meta_OGG_SLI, /* Ogg Vorbis file w/ companion .sli for looping */ meta_OGG_SLI, /* Ogg Vorbis file w/ companion .sli for looping */
meta_OGG_SLI2, /* Ogg Vorbis file w/ different styled .sli for looping */ meta_OGG_SLI2, /* Ogg Vorbis file w/ different styled .sli for looping */
meta_OGG_SFL, /* Ogg Vorbis file w/ .sfl (RIFF SFPL) for looping */ meta_OGG_SFL, /* Ogg Vorbis file w/ .sfl (RIFF SFPL) for looping */
meta_OGG_UM3, /* Ogg Vorbis with first 0x800 bytes XOR 0xFF */ meta_OGG_UM3, /* Ogg Vorbis with optional first 0x800 bytes XOR 0xFF */
meta_OGG_KOVS, /* Ogg Vorbis with exta header and 0x100 bytes XOR */ meta_OGG_KOVS, /* Ogg Vorbis with extra header and 0x100 bytes XOR */
meta_OGG_PSYCH, /* Ogg Vorbis with all bytes -0x23*/ meta_OGG_PSYCHIC, /* Ogg Vorbis with all bytes -0x23 */
#endif meta_OGG_SNGW, /* Ogg Vorbis with optional key XOR + nibble swap (Capcom PC games) */
meta_OGG_ISD, /* Ogg Vorbis with key XOR (Azure Striker Gunvolt PC) */
meta_KMA9, /* Koei Tecmo [Nobunaga no Yabou - Souzou (Vita)] */
#ifdef VGM_USE_MP4V2 #ifdef VGM_USE_MP4V2
meta_MP4, /* AAC (iOS) */ meta_MP4, /* AAC (iOS) */
#endif #endif
@ -794,15 +796,16 @@ typedef struct {
/* Ogg with Vorbis */ /* Ogg with Vorbis */
typedef struct { typedef struct {
STREAMFILE *streamfile; STREAMFILE *streamfile;
ogg_int64_t offset; ogg_int64_t start; /* file offset where the Ogg starts */
ogg_int64_t size; ogg_int64_t offset; /* virtual offset, from 0 to size */
ogg_int64_t other_header_bytes; ogg_int64_t size; /* virtual size of the Ogg */
/* 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;
uint32_t sngw_xor;
} ogg_vorbis_streamfile; } ogg_vorbis_streamfile;
typedef struct { typedef struct {
@ -994,6 +997,7 @@ typedef struct {
typedef enum { typedef enum {
ATRAC9_DEFAULT = 0, /* ATRAC9 standard */ ATRAC9_DEFAULT = 0, /* ATRAC9 standard */
ATRAC9_XVAG, /* Sony XVAG: interleaved subsongs, Vita multichannel interleaves 2ch xN superframes */ ATRAC9_XVAG, /* Sony XVAG: interleaved subsongs, Vita multichannel interleaves 2ch xN superframes */
ATRAC9_KMA9, /* Koei Tecmo KMA9: interleaved subsongs */
//ATRAC9_FSB, /* FMOD FSB: Vita multichannel interleaves 2ch xN superframes */ //ATRAC9_FSB, /* FMOD FSB: Vita multichannel interleaves 2ch xN superframes */
//ATRAC9_EATRAX, /* EA EATrax: buffered ATRAC9 in SPS blocks (superframes can be split between blocks) */ //ATRAC9_EATRAX, /* EA EATrax: buffered ATRAC9 in SPS blocks (superframes can be split between blocks) */
} atrac9_custom_t; } atrac9_custom_t;