mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-30 20:03:44 +01:00
Merge pull request #174 from bnnm/scd-xvag-sngw-isd-km9
SCD, XVAG, SNGW, ISD, KM9
This commit is contained in:
commit
40ffa3e1d6
@ -119,7 +119,9 @@ void decode_atrac9(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do,
|
||||
|
||||
/* postadjust */ //todo improve
|
||||
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)) {
|
||||
stream->offset += data->config.interleave_skip * (data->config.subsong_skip - 1);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ static const char* extension_list[] = {
|
||||
"aaap",
|
||||
"aax",
|
||||
//"ac3", //FFmpeg, not parsed //common?
|
||||
"ace", //fake, for tri-Ace's formats
|
||||
"ace", //fake, for tri-Ace's formats (to be removed)
|
||||
"acm",
|
||||
"adm",
|
||||
"adp",
|
||||
@ -33,7 +33,7 @@ static const char* extension_list[] = {
|
||||
"aix",
|
||||
"akb",
|
||||
"al2",
|
||||
"amts", //fake extension (to be removed)
|
||||
"amts", //fake extension/header id for .stm (to be removed)
|
||||
"ao", //txth/reserved [Cloudphobia (PC)]
|
||||
"as4",
|
||||
"asd",
|
||||
@ -138,7 +138,7 @@ static const char* extension_list[] = {
|
||||
"iab",
|
||||
"iadp",
|
||||
"idsp",
|
||||
"idvi", //fake extension (to be removed)
|
||||
"idvi", //fake extension for .pcm (to be removed)
|
||||
"ikm",
|
||||
"ild",
|
||||
"int",
|
||||
@ -153,9 +153,10 @@ static const char* extension_list[] = {
|
||||
"jstm",
|
||||
|
||||
"kces",
|
||||
"kcey", //fake extension (to be removed)
|
||||
"kcey", //fake extension/header id (to be removed)
|
||||
"khv",
|
||||
"kovs",
|
||||
"km9",
|
||||
"kovs", //.kvs header id
|
||||
"kraw",
|
||||
"ktss",
|
||||
"kvs",
|
||||
@ -206,7 +207,7 @@ static const char* extension_list[] = {
|
||||
"ndp",
|
||||
"ngca",
|
||||
"nps",
|
||||
"npsf", //fake extension (to be removed)
|
||||
"npsf", //fake extension/header id for .nps (to be removed)
|
||||
"nus3bank",
|
||||
"nwa",
|
||||
|
||||
@ -226,7 +227,7 @@ static const char* extension_list[] = {
|
||||
"pnb",
|
||||
"pona",
|
||||
"pos",
|
||||
"ps2stm", //fake extension (to be removed)
|
||||
"ps2stm", //fake extension for .stm (to be removed)
|
||||
"psh",
|
||||
"psnd",
|
||||
"psw",
|
||||
@ -282,6 +283,7 @@ static const char* extension_list[] = {
|
||||
"snd",
|
||||
"snds",
|
||||
"sng",
|
||||
"sngw",
|
||||
"snr",
|
||||
"sns",
|
||||
"snu",
|
||||
@ -574,6 +576,7 @@ static const layout_info layout_info_list[] = {
|
||||
{layout_blocked_awc, "blocked (AWC)"},
|
||||
{layout_blocked_vgs, "blocked (VGS)"},
|
||||
{layout_blocked_vawx, "blocked (VAWX)"},
|
||||
{layout_blocked_xvag_subsong, "blocked (XVAG subsong)"},
|
||||
#ifdef VGM_USE_VORBIS
|
||||
{layout_ogg_vorbis, "Ogg"},
|
||||
#endif
|
||||
@ -933,16 +936,17 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_NGC_VID1, "Neversoft VID1 header"},
|
||||
{meta_PC_FLX, "Ultima IX .FLX header"},
|
||||
{meta_MOGG, "Harmonix Music Systems MOGG Vorbis"},
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
{meta_OGG_VORBIS, "Ogg Vorbis"},
|
||||
{meta_OGG_SLI, "Ogg Vorbis with .sli (start,length) for looping"},
|
||||
{meta_OGG_SLI2, "Ogg Vorbis with .sli (from,to) for looping"},
|
||||
{meta_OGG_SFL, "Ogg Vorbis with SFPL for looping"},
|
||||
{meta_OGG_UM3, "Ogg Vorbis, Ultramarine3 'encryption'"},
|
||||
{meta_OGG_KOVS, "Ogg Vorbis, KOVS header"},
|
||||
{meta_OGG_PSYCH, "Ogg Vorbis, Psychic Software obfuscation"},
|
||||
#endif
|
||||
{meta_OGG_UM3, "Ogg Vorbis (Ultramarine3)"},
|
||||
{meta_OGG_KOVS, "Ogg Vorbis (KOVS header)"},
|
||||
{meta_OGG_PSYCHIC, "Ogg Vorbis (Psychic Software)"},
|
||||
{meta_OGG_SNGW, "Ogg Vorbis (Capcom)"},
|
||||
{meta_OGG_ISD, "Ogg Vorbis (ISD)"},
|
||||
{meta_KMA9, "Koei Tecmo KMA9 header"},
|
||||
|
||||
#ifdef VGM_USE_MP4V2
|
||||
{meta_MP4, "AAC header"},
|
||||
#endif
|
||||
|
@ -164,6 +164,9 @@ void render_vgmstream_blocked(sample * buffer, int32_t sample_count, VGMSTREAM *
|
||||
case layout_blocked_vawx:
|
||||
block_update_vawx(vgmstream->next_block_offset,vgmstream);
|
||||
break;
|
||||
case layout_blocked_xvag_subsong:
|
||||
block_update_xvag_subsong(vgmstream->next_block_offset,vgmstream);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
19
src/layout/blocked_xvag.c
Normal file
19
src/layout/blocked_xvag.c
Normal 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;
|
||||
}
|
@ -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_vgs(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 */
|
||||
void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
|
@ -472,6 +472,10 @@
|
||||
RelativePath=".\meta\ivb.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\kma9.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\kraw.c"
|
||||
>
|
||||
@ -679,7 +683,7 @@
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\ogg_vorbis_file.c"
|
||||
RelativePath=".\meta\ogg_vorbis.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
@ -1669,6 +1673,10 @@
|
||||
<File
|
||||
RelativePath=".\layout\blocked_vgs.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\layout\blocked_xvag.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\layout\caf_blocked.c"
|
||||
|
@ -226,6 +226,7 @@
|
||||
<ClCompile Include="meta\ish_isd.c" />
|
||||
<ClCompile Include="meta\ivaud.c" />
|
||||
<ClCompile Include="meta\ivb.c" />
|
||||
<ClCompile Include="meta\kma9.c" />
|
||||
<ClCompile Include="meta\kraw.c" />
|
||||
<ClCompile Include="meta\maxis_xa.c" />
|
||||
<ClCompile Include="meta\mc3.c" />
|
||||
@ -270,7 +271,7 @@
|
||||
<ClCompile Include="meta\ngc_vid1.c" />
|
||||
<ClCompile Include="meta\nub_xma.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\omu.c" />
|
||||
<ClCompile Include="meta\otm.c" />
|
||||
@ -464,6 +465,7 @@
|
||||
<ClCompile Include="layout\blocked_ea_1snh.c" />
|
||||
<ClCompile Include="layout\blocked_vgs.c" />
|
||||
<ClCompile Include="layout\blocked_vawx.c" />
|
||||
<ClCompile Include="layout\blocked_xvag.c" />
|
||||
<ClCompile Include="layout\caf_blocked.c" />
|
||||
<ClCompile Include="layout\blocked_dec.c" />
|
||||
<ClCompile Include="layout\blocked_ea_schl.c" />
|
||||
|
@ -283,6 +283,9 @@
|
||||
<ClCompile Include="meta\ivb.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\kma9.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\kraw.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -400,7 +403,7 @@
|
||||
<ClCompile Include="meta\nwa.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\ogg_vorbis_file.c">
|
||||
<ClCompile Include="meta\ogg_vorbis.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\ogl.c">
|
||||
@ -973,6 +976,9 @@
|
||||
<ClCompile Include="layout\blocked_vawx.c">
|
||||
<Filter>layout\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="layout\blocked_xvag.c">
|
||||
<Filter>layout\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="layout\caf_blocked.c">
|
||||
<Filter>layout\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
78
src/meta/kma9.c
Normal file
78
src/meta/kma9.c
Normal 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;
|
||||
}
|
@ -99,11 +99,14 @@ typedef struct {
|
||||
meta_t meta_type;
|
||||
layout_t layout_type;
|
||||
|
||||
/* XOR setup (SCD) */
|
||||
int decryption_enabled;
|
||||
void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource, int bytes_read);
|
||||
off_t stream_size;
|
||||
int total_subsongs;
|
||||
|
||||
/* decryption setup */
|
||||
void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource);
|
||||
uint8_t scd_xor;
|
||||
off_t scd_xor_length;
|
||||
uint32_t sngw_xor;
|
||||
|
||||
} vgm_vorbis_info_t;
|
||||
|
||||
@ -690,4 +693,6 @@ VGMSTREAM * init_vgmstream_flx(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_mogg(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_kma9(STREAMFILE * streamFile);
|
||||
|
||||
#endif /*_META_H*/
|
||||
|
453
src/meta/ogg_vorbis.c
Normal file
453
src/meta/ogg_vorbis.c
Normal 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
|
@ -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
|
@ -4,20 +4,19 @@
|
||||
|
||||
|
||||
#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_decrypt_v3_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_v3_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource);
|
||||
#endif
|
||||
|
||||
/* SCD - Square-Enix console games (FF XIII, XIV) */
|
||||
/* SCD - Square-Enix games (FF XIII, XIV) */
|
||||
VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
char filename[PATH_LIMIT];
|
||||
off_t start_offset, tables_offset, headers_offset, meta_offset, post_meta_offset;
|
||||
int headers_entries;
|
||||
int32_t stream_size, loop_start, loop_end;
|
||||
off_t start_offset, tables_offset, meta_offset, post_meta_offset;
|
||||
int32_t stream_size, subheader_size, loop_start, loop_end;
|
||||
|
||||
int target_stream = streamFile->stream_index;
|
||||
int loop_flag = 0, channel_count, codec_id, sample_rate;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
int loop_flag = 0, channel_count, codec, sample_rate;
|
||||
int aux_chunk_count;
|
||||
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
||||
@ -25,32 +24,34 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
|
||||
|
||||
|
||||
/* 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));
|
||||
|
||||
/* SEDB */
|
||||
if (read_32bitBE(0x00,streamFile) != 0x53454442) goto fail;
|
||||
/* SSCF */
|
||||
if (read_32bitBE(0x04,streamFile) != 0x53534346) goto fail;
|
||||
/** main header **/
|
||||
if (read_32bitBE(0x00,streamFile) != 0x53454442 && /* "SEDB" */
|
||||
read_32bitBE(0x04,streamFile) != 0x53534346) /* "SSCF" */
|
||||
goto fail;
|
||||
|
||||
/** main header section **/
|
||||
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 */
|
||||
|
||||
//size_offset = 0x14;
|
||||
read_32bit = read_32bitBE;
|
||||
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?) */
|
||||
read_32bitLE(0x08,streamFile) == 2) {
|
||||
|
||||
}
|
||||
else if (read_32bitLE(0x08,streamFile) == 2 || /* version 2/3 LE, as seen in FFXIV for PC (and others) */
|
||||
read_32bitLE(0x08,streamFile) == 3) {
|
||||
//size_offset = 0x10;
|
||||
read_32bit = read_32bitLE;
|
||||
read_16bit = read_16bitLE;
|
||||
//size_offset = 0x10;
|
||||
} else goto fail;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* 0x0c: probably 0=LE, 1=BE */
|
||||
/* 0x0d: unk (always 0x04) */
|
||||
tables_offset = read_16bit(0xe,streamFile);
|
||||
/* 0x0c: probably 0=LE, 1=BE */
|
||||
/* 0x0d: unknown (always 0x04) */
|
||||
tables_offset = read_16bit(0x0e,streamFile); /* usually 0x30 or 0x20 */
|
||||
|
||||
#if 0
|
||||
/* never mind, FFXIII music_68tak.ps3.scd is 0x80 shorter */
|
||||
@ -59,43 +60,66 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
|
||||
goto fail;
|
||||
#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) **/
|
||||
meta_offset = read_32bit(headers_offset + (target_stream-1)*4,streamFile);
|
||||
/* (implicit: table1 starts at 0x20) */
|
||||
/* 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_size = read_32bit(meta_offset+0x00,streamFile);
|
||||
channel_count = read_32bit(meta_offset+0x04,streamFile);
|
||||
sample_rate = read_32bit(meta_offset+0x08,streamFile);
|
||||
codec_id = read_32bit(meta_offset+0x0c,streamFile);
|
||||
stream_size = read_32bit(meta_offset+0x00,streamFile);
|
||||
channel_count = read_32bit(meta_offset+0x04,streamFile);
|
||||
sample_rate = read_32bit(meta_offset+0x08,streamFile);
|
||||
codec = read_32bit(meta_offset+0x0c,streamFile);
|
||||
|
||||
loop_start = read_32bit(meta_offset+0x10,streamFile);
|
||||
loop_end = read_32bit(meta_offset+0x14,streamFile);
|
||||
loop_flag = (loop_end > 0);
|
||||
|
||||
post_meta_offset = meta_offset + 0x20;
|
||||
start_offset = post_meta_offset + read_32bit(meta_offset+0x18,streamFile);
|
||||
loop_start = read_32bit(meta_offset+0x10,streamFile);
|
||||
loop_end = read_32bit(meta_offset+0x14,streamFile);
|
||||
subheader_size = read_32bit(meta_offset+0x18,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) */
|
||||
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);
|
||||
goto fail;
|
||||
}
|
||||
@ -107,73 +131,52 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
|
||||
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
/* special case using init_vgmstream_ogg_vorbis with callbacks */
|
||||
if (codec_id == 0x06) {
|
||||
VGMSTREAM * result = NULL;
|
||||
uint32_t seek_table_size, vorb_header_size;
|
||||
uint8_t xor_version, xor_byte;
|
||||
/* special case using init_vgmstream_ogg_vorbis */
|
||||
if (codec == 0x06) {
|
||||
uint8_t ogg_version, ogg_byte;
|
||||
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.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
|
||||
* (seems that encrypted = always seek table, but maybe post_meta_offset+0x01 = 0x20) */
|
||||
ogg_version = read_8bit(post_meta_offset + 0x00, streamFile);
|
||||
/* 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 */
|
||||
{
|
||||
result = init_vgmstream_ogg_vorbis_callbacks(streamFile, filename, NULL, start_offset, &inf);
|
||||
if (result != NULL)
|
||||
return result;
|
||||
if (ogg_version == 0) { /* 0x10? header, then custom Vorbis header before regular Ogg (FF XIV PC v1) */
|
||||
inf.stream_size = stream_size;
|
||||
}
|
||||
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 */
|
||||
{
|
||||
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;
|
||||
}
|
||||
if ((post_meta_offset-meta_offset) + seek_table_size + vorb_header_size != subheader_size)
|
||||
goto fail;
|
||||
|
||||
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 (result != NULL)
|
||||
return result;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
if (ogg_version == 2) { /* header is XOR'ed using byte (FF XIV PC) */
|
||||
inf.decryption_callback = scd_ogg_v2_decryption_callback;
|
||||
inf.scd_xor = read_8bit(post_meta_offset + 0x02, streamFile);
|
||||
inf.scd_xor_length = vorb_header_size;
|
||||
}
|
||||
else if (xor_version == 3) { /* full file is XOR'ed using table */
|
||||
inf.decryption_enabled = 1;
|
||||
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_length = stream_size;
|
||||
else if (ogg_version == 3) { /* file is XOR'ed using table (FF XIV Heavensward PC) */
|
||||
inf.decryption_callback = scd_ogg_v3_decryption_callback;
|
||||
inf.scd_xor = stream_size & 0xFF; /* ogg_byte not used? */
|
||||
inf.scd_xor_length = vorb_header_size + stream_size;
|
||||
}
|
||||
else {
|
||||
VGM_LOG("SCD: unknown encryption 0x%x\n", xor_version);
|
||||
return NULL;
|
||||
VGM_LOG("SCD: unknown ogg_version 0x%x\n", ogg_version);
|
||||
}
|
||||
|
||||
/* 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
|
||||
|
||||
@ -185,18 +188,31 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
|
||||
/* fill in the vital statistics */
|
||||
vgmstream->channels = channel_count;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_streams = headers_entries;
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->meta_type = meta_SQEX_SCD;
|
||||
|
||||
switch (codec_id) {
|
||||
switch (codec) {
|
||||
case 0x01: /* PCM */
|
||||
vgmstream->coding_type = coding_PCM16_int;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->num_samples = stream_size / 2 / channel_count;
|
||||
vgmstream->coding_type = coding_PCM16LE;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x02;
|
||||
|
||||
vgmstream->num_samples = pcm_bytes_to_samples(stream_size, channel_count, 16);
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = loop_start / 2 / channel_count;
|
||||
vgmstream->loop_end_sample = loop_end / 2 / channel_count;
|
||||
vgmstream->loop_start_sample = pcm_bytes_to_samples(loop_start, channel_count, 16);
|
||||
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;
|
||||
|
||||
@ -234,6 +250,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
|
||||
vgmstream->coding_type = coding_MSADPCM;
|
||||
vgmstream->layout_type = layout_none;
|
||||
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);
|
||||
if (loop_flag) {
|
||||
@ -324,7 +341,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
|
||||
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;
|
||||
|
||||
/* full RIFF header at start_offset/post_meta_offset (same) */
|
||||
@ -357,8 +374,9 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
|
||||
}
|
||||
#endif
|
||||
|
||||
case -1: /* used for dummy entries */
|
||||
default:
|
||||
VGM_LOG("SCD: unknown codec_id 0x%x\n", codec_id);
|
||||
VGM_LOG("SCD: unknown codec 0x%x\n", codec);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@ -375,9 +393,14 @@ fail:
|
||||
|
||||
|
||||
#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;
|
||||
|
||||
/* no encryption, sometimes happens */
|
||||
if (ov_streamfile->scd_xor == 0x00)
|
||||
return;
|
||||
|
||||
/* header is XOR'd with a constant byte */
|
||||
if (ov_streamfile->offset < ov_streamfile->scd_xor_length) {
|
||||
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) {
|
||||
/* V3 decryption table found in the .exe */
|
||||
static const uint8_t scd_ogg_v3_lookuptable[256] = { /* FF XIV Heavensward */
|
||||
static void scd_ogg_v3_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
||||
/* V3 decryption table found in the .exe of 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
|
||||
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
|
||||
@ -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
|
||||
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;
|
||||
|
||||
/* 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;
|
||||
uint8_t byte1, byte2, xorByte;
|
||||
uint8_t byte1, byte2, xor_byte;
|
||||
|
||||
num_crypt = bytes_read;
|
||||
byte1 = ov_streamfile->scd_xor & 0x7F;
|
||||
byte2 = ov_streamfile->scd_xor & 0x3F;
|
||||
|
||||
for (i = 0; i < num_crypt; i++) {
|
||||
xorByte = scd_ogg_v3_lookuptable[(byte2 + ov_streamfile->offset + i) & 0xFF];
|
||||
xorByte &= 0xFF;
|
||||
xorByte ^= ((uint8_t*)ptr)[i];
|
||||
xorByte ^= byte1;
|
||||
((uint8_t*)ptr)[i] = (uint8_t)xorByte;
|
||||
xor_byte = scd_ogg_v3_lookuptable[(byte2 + ov_streamfile->offset + i) & 0xFF];
|
||||
xor_byte &= 0xFF;
|
||||
xor_byte ^= ((uint8_t*)ptr)[i];
|
||||
xor_byte ^= byte1;
|
||||
((uint8_t*)ptr)[i] = (uint8_t)xor_byte;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "meta.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);
|
||||
|
||||
@ -10,10 +11,10 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
||||
int loop_flag = 0, channel_count, codec;
|
||||
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;
|
||||
|
||||
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;
|
||||
size_t chunk_size;
|
||||
|
||||
@ -37,7 +38,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
|
||||
/* 0x08: flags? (&0x01=big endian, 0x02=?, 0x06=full RIFF AT9?)
|
||||
* 0x09: flags2? (0x00/0x01/0x04, speaker mode?)
|
||||
* 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) */
|
||||
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? */
|
||||
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);
|
||||
/* 0x18: datasize */
|
||||
|
||||
/* extra data, seen in MPEG/ATRAC9 */
|
||||
/* extra data, seen in versions 0x61+ */
|
||||
if (chunk_size > 0x1c) {
|
||||
total_subsongs = read_32bit(chunk_offset+0x1c,streamFile); /* number of interleaved layers */
|
||||
multistreams = read_32bit(chunk_offset+0x20,streamFile); /* number of bitstreams per layer (for multistream Nch MPEG/ATRAC9) */
|
||||
/* number of interleaved subsong layers */
|
||||
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 > total_subsongs || total_subsongs < 1) goto fail;
|
||||
@ -66,9 +73,9 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
|
||||
/* "cues": cue/labels (rare) */
|
||||
/* "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
|
||||
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);
|
||||
}
|
||||
|
||||
@ -82,27 +89,41 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
|
||||
vgmstream->meta_type = meta_XVAG;
|
||||
|
||||
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 0x07: /* SVAG? (PS ADPCM with extended table): inFamous 1 (PS3) */
|
||||
if (total_subsongs > 1 || multistreams > 1) goto fail;
|
||||
if (multiplier > 1) goto fail;
|
||||
if (multistreams > 1 && multistreams != vgmstream->channels) 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;
|
||||
|
||||
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) {
|
||||
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, vgmstream->channels);
|
||||
vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, vgmstream->channels);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
case 0x08: { /* MPEG: The Last of Us (PS3), Uncharted 3 (PS3), Medieval Moves (PS3) */
|
||||
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 */
|
||||
/* 0x00/04: mpeg version/layer? other: unknown or repeats of "fmat" */
|
||||
@ -110,7 +131,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
|
||||
goto fail;
|
||||
|
||||
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);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
@ -138,7 +159,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
|
||||
}
|
||||
else if (total_subsongs > 1) {
|
||||
/* 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;
|
||||
/* start in subsong's first superframe */
|
||||
start_offset += (target_subsong-1) * cfg.interleave_skip * (cfg.subsong_skip-1);
|
||||
@ -158,6 +179,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
|
||||
}
|
||||
#endif
|
||||
|
||||
//case 0x??: /* PCM? */
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
@ -167,6 +189,9 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
|
||||
if (vgmstream->layout_type == layout_blocked_xvag_subsong)
|
||||
block_update_xvag_subsong(start_offset, vgmstream);
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
|
@ -373,6 +373,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
|
||||
init_vgmstream_ngc_vid1,
|
||||
init_vgmstream_flx,
|
||||
init_vgmstream_mogg,
|
||||
init_vgmstream_kma9,
|
||||
|
||||
init_vgmstream_txth, /* should go at the end (lower priority) */
|
||||
#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_vgs:
|
||||
case layout_blocked_vawx:
|
||||
case layout_blocked_xvag_subsong:
|
||||
render_vgmstream_blocked(buffer,sample_count,vgmstream);
|
||||
break;
|
||||
case layout_acm:
|
||||
|
@ -251,6 +251,7 @@ typedef enum {
|
||||
layout_blocked_awc, /* Rockstar AWC */
|
||||
layout_blocked_vgs, /* Guitar Hero II (PS2) */
|
||||
layout_blocked_vawx, /* No More Heroes 6ch (PS3) */
|
||||
layout_blocked_xvag_subsong, /* XVAG subsongs [God of War III (PS4)] */
|
||||
|
||||
/* otherwise odd */
|
||||
layout_acm, /* libacm layout */
|
||||
@ -645,16 +646,17 @@ typedef enum {
|
||||
meta_NGC_VID1, /* Neversoft .ogg (Gun GC) */
|
||||
meta_PC_FLX, /* Ultima IX PC */
|
||||
meta_MOGG, /* Harmonix Music Systems MOGG Vorbis */
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
meta_OGG_VORBIS, /* Ogg Vorbis */
|
||||
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_SFL, /* Ogg Vorbis file w/ .sfl (RIFF SFPL) for looping */
|
||||
meta_OGG_UM3, /* Ogg Vorbis with first 0x800 bytes XOR 0xFF */
|
||||
meta_OGG_KOVS, /* Ogg Vorbis with exta header and 0x100 bytes XOR */
|
||||
meta_OGG_PSYCH, /* Ogg Vorbis with all bytes -0x23*/
|
||||
#endif
|
||||
meta_OGG_UM3, /* Ogg Vorbis with optional first 0x800 bytes XOR 0xFF */
|
||||
meta_OGG_KOVS, /* Ogg Vorbis with extra header and 0x100 bytes XOR */
|
||||
meta_OGG_PSYCHIC, /* Ogg Vorbis with all bytes -0x23 */
|
||||
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
|
||||
meta_MP4, /* AAC (iOS) */
|
||||
#endif
|
||||
@ -794,15 +796,16 @@ typedef struct {
|
||||
/* Ogg with Vorbis */
|
||||
typedef struct {
|
||||
STREAMFILE *streamfile;
|
||||
ogg_int64_t offset;
|
||||
ogg_int64_t size;
|
||||
ogg_int64_t other_header_bytes;
|
||||
ogg_int64_t start; /* file offset where the Ogg starts */
|
||||
ogg_int64_t offset; /* virtual offset, from 0 to size */
|
||||
ogg_int64_t size; /* virtual size of the Ogg */
|
||||
|
||||
/* XOR setup (SCD) */
|
||||
int decryption_enabled;
|
||||
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;
|
||||
off_t scd_xor_length;
|
||||
uint32_t sngw_xor;
|
||||
|
||||
} ogg_vorbis_streamfile;
|
||||
|
||||
typedef struct {
|
||||
@ -994,6 +997,7 @@ typedef struct {
|
||||
typedef enum {
|
||||
ATRAC9_DEFAULT = 0, /* ATRAC9 standard */
|
||||
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_EATRAX, /* EA EATrax: buffered ATRAC9 in SPS blocks (superframes can be split between blocks) */
|
||||
} atrac9_custom_t;
|
||||
|
Loading…
x
Reference in New Issue
Block a user