Updated VGMStream to r1050-2345-g1d28e0f5
parent
73b7f3651f
commit
7657eb4971
|
@ -213,6 +213,8 @@
|
|||
8351F32D2212B57000A606E4 /* 208.c in Sources */ = {isa = PBXBuildFile; fileRef = 8351F32A2212B57000A606E4 /* 208.c */; };
|
||||
8351F32E2212B57000A606E4 /* ubi_bao_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 8351F32B2212B57000A606E4 /* ubi_bao_streamfile.h */; };
|
||||
8351F32F2212B57000A606E4 /* dsf.c in Sources */ = {isa = PBXBuildFile; fileRef = 8351F32C2212B57000A606E4 /* dsf.c */; };
|
||||
835C883622CC17BE001B4B3F /* bwav.c in Sources */ = {isa = PBXBuildFile; fileRef = 835C883122CC17BD001B4B3F /* bwav.c */; };
|
||||
835C883722CC17BE001B4B3F /* ogg_vorbis_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 835C883522CC17BE001B4B3F /* ogg_vorbis_streamfile.h */; };
|
||||
836F6B4718BDB8880095E648 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 836F6B4518BDB8880095E648 /* InfoPlist.strings */; };
|
||||
836F6F1E18BDC2190095E648 /* acm_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6DE018BDC2180095E648 /* acm_decoder.c */; };
|
||||
836F6F2018BDC2190095E648 /* adx_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6DE218BDC2180095E648 /* adx_decoder.c */; };
|
||||
|
@ -873,6 +875,8 @@
|
|||
8351F32A2212B57000A606E4 /* 208.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = 208.c; sourceTree = "<group>"; };
|
||||
8351F32B2212B57000A606E4 /* ubi_bao_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ubi_bao_streamfile.h; sourceTree = "<group>"; };
|
||||
8351F32C2212B57000A606E4 /* dsf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dsf.c; sourceTree = "<group>"; };
|
||||
835C883122CC17BD001B4B3F /* bwav.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bwav.c; sourceTree = "<group>"; };
|
||||
835C883522CC17BE001B4B3F /* ogg_vorbis_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ogg_vorbis_streamfile.h; sourceTree = "<group>"; };
|
||||
836F6B3918BDB8880095E648 /* libvgmstream.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = libvgmstream.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
836F6B4418BDB8880095E648 /* libvgmstream-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "libvgmstream-Info.plist"; sourceTree = "<group>"; };
|
||||
836F6B4618BDB8880095E648 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
|
@ -1526,6 +1530,7 @@
|
|||
836F6E3918BDC2180095E648 /* bnsf.c */,
|
||||
836F6E3A18BDC2180095E648 /* brstm.c */,
|
||||
83EDE5D71A70951A005F5D84 /* btsnd.c */,
|
||||
835C883122CC17BD001B4B3F /* bwav.c */,
|
||||
8306B0CF2098458F000302D4 /* caf.c */,
|
||||
836F6E3B18BDC2180095E648 /* capdsp.c */,
|
||||
834FE0E8215C79EC000A5D3D /* ck.c */,
|
||||
|
@ -1657,6 +1662,7 @@
|
|||
834FE0DB215C79EA000A5D3D /* nxa.c */,
|
||||
8306B0C02098458C000302D4 /* nxap.c */,
|
||||
832BF81321E05149006F50F1 /* ogg_opus.c */,
|
||||
835C883522CC17BE001B4B3F /* ogg_vorbis_streamfile.h */,
|
||||
83A21F7F201D8980000F04B9 /* ogg_vorbis.c */,
|
||||
831BA60F1EAC61A500CF89B0 /* ogl.c */,
|
||||
8349A8FB1FE6257F00E26435 /* omu.c */,
|
||||
|
@ -1920,6 +1926,7 @@
|
|||
832BF82C21E0514B006F50F1 /* hca_keys_awb.h in Headers */,
|
||||
8306B0E020984590000302D4 /* ppst_streamfile.h in Headers */,
|
||||
834FE0BA215C798C000A5D3D /* ea_mt_decoder_utk.h in Headers */,
|
||||
835C883722CC17BE001B4B3F /* ogg_vorbis_streamfile.h in Headers */,
|
||||
832BF82021E0514B006F50F1 /* zsnd_streamfile.h in Headers */,
|
||||
8375737721F950ED00F01AF5 /* ubi_sb_streamfile.h in Headers */,
|
||||
839E21E01F2EDAF100EE54D7 /* vorbis_custom_data_fsb.h in Headers */,
|
||||
|
@ -2200,6 +2207,7 @@
|
|||
8306B0ED20984590000302D4 /* txtp.c in Sources */,
|
||||
836F6FA018BDC2190095E648 /* musx.c in Sources */,
|
||||
836F705818BDC2190095E648 /* vgmstream.c in Sources */,
|
||||
835C883622CC17BE001B4B3F /* bwav.c in Sources */,
|
||||
8349A90A1FE6258200E26435 /* sab.c in Sources */,
|
||||
836F6F6818BDC2190095E648 /* ads.c in Sources */,
|
||||
8306B08620984518000302D4 /* fadpcm_decoder.c in Sources */,
|
||||
|
|
|
@ -78,18 +78,23 @@ int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, m
|
|||
|
||||
//todo: test more: this improves the output, but seems formats aren't usually prepared
|
||||
// (and/or the num_samples includes all possible samples in file, so by discarding some it'll reach EOF)
|
||||
#if 0
|
||||
|
||||
/* set encoder delay (samples to skip at the beginning of a stream) if needed, which varies with encoder used */
|
||||
switch(data->type) {
|
||||
case MPEG_AHX: data->skip_samples = 480; break; /* observed default */
|
||||
case MPEG_P3D: data->skip_samples = info.frame_samples; break; /* matches Radical ADPCM (PC) output */
|
||||
//case MPEG_AHX: data->skip_samples = 480; break; /* observed default */
|
||||
//case MPEG_P3D: data->skip_samples = info.frame_samples; break; /* matches Radical ADPCM (PC) output */
|
||||
|
||||
/* FSBs (with FMOD DLLs) don't seem to need it. Particularly a few games (all from Wayforward?)
|
||||
* contain audible garbage at the beginning, but it's actually there in-game too */
|
||||
case MPEG_FSB: data->skip_samples = 0;
|
||||
default: break;
|
||||
//case MPEG_FSB: data->skip_samples = 0; break;
|
||||
|
||||
case MPEG_XVAG: /* set in header and needed for gapless looping */
|
||||
data->skip_samples = data->config.skip_samples; break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
data->samples_to_discard = data->skip_samples;
|
||||
#endif
|
||||
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
|
|
|
@ -101,6 +101,7 @@ static const char* extension_list[] = {
|
|||
"brstmspm",
|
||||
"btsnd",
|
||||
"bvg",
|
||||
"bwav",
|
||||
|
||||
"caf",
|
||||
"capdsp",
|
||||
|
@ -1189,6 +1190,7 @@ static const meta_info meta_info_list[] = {
|
|||
{meta_MSF_KONAMI, "Konami MSF header"},
|
||||
{meta_XWMA_KONAMI, "Konami XWMA header"},
|
||||
{meta_9TAV, "Konami 9TAV header"},
|
||||
{meta_BWAV, "Nintendo BWAV header"},
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* BWAV - NintendoWare(?) [Super Mario Maker 2 (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_bwav(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int channel_count, loop_flag;
|
||||
size_t interleave = 0;
|
||||
int32_t coef_start_offset, coef_spacing;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "bwav"))
|
||||
goto fail;
|
||||
|
||||
/* BWAV header */
|
||||
if (read_32bitBE(0x00, streamFile) != 0x42574156) /* "BWAV" */
|
||||
goto fail;
|
||||
|
||||
channel_count = read_16bitLE(0x0E, streamFile);
|
||||
start_offset = read_32bitLE(0x40, streamFile);
|
||||
loop_flag = read_32bitLE(0x4C, streamFile);
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = read_32bitLE(0x14, streamFile);
|
||||
vgmstream->num_samples = read_32bitLE(0x18, streamFile);
|
||||
vgmstream->loop_start_sample = read_32bitLE(0x50, streamFile);
|
||||
vgmstream->loop_end_sample = read_32bitLE(0x4C, streamFile);
|
||||
vgmstream->meta_type = meta_BWAV;
|
||||
vgmstream->layout_type = (channel_count == 1) ? layout_none : layout_interleave;
|
||||
vgmstream->interleave_block_size = read_32bitLE(0x8C, streamFile) - start_offset;
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
|
||||
coef_start_offset = 0x20;
|
||||
coef_spacing = 0x4C;
|
||||
dsp_read_coefs_le(vgmstream, streamFile, coef_start_offset, coef_spacing);
|
||||
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -35,9 +35,14 @@ VGMSTREAM * init_vgmstream_hd3_bd3(STREAMFILE *streamFile) {
|
|||
if (read_32bitBE(section_offset+0x00,streamHeader) != 0x50335641) /* "P3VA" */
|
||||
goto fail;
|
||||
section_size = read_32bitBE(section_offset+0x04,streamHeader); /* (not including first 0x08) */
|
||||
/* 0x08 always 0x10? */
|
||||
entries = read_32bitBE(section_offset+0x14,streamHeader) + 1;
|
||||
if (entries != (section_size-0x18) / 0x10)
|
||||
/* 0x08 size of all subsong headers + 0x10 */
|
||||
|
||||
entries = read_32bitBE(section_offset+0x14,streamHeader);
|
||||
/* often there is an extra subsong than written, but may be padding instead */
|
||||
if (read_32bitBE(section_offset + 0x20 + entries*0x10 + 0x04,streamHeader)) /* has sample rate */
|
||||
entries += 1;
|
||||
|
||||
if (entries * 0x10 > section_size) /* just in case, padding after entries is possible */
|
||||
goto fail;
|
||||
|
||||
/* autodetect use of N bank entries as channels [Elevator Action Deluxe (PS3)] */
|
||||
|
|
|
@ -854,4 +854,6 @@ VGMSTREAM * init_vgmstream_9tav(STREAMFILE* streamFile);
|
|||
|
||||
VGMSTREAM * init_vgmstream_fsb5_fev_bank(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_bwav(STREAMFILE * streamFile);
|
||||
|
||||
#endif /*_META_H*/
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
#ifdef VGM_USE_VORBIS
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "meta.h"
|
||||
#include <vorbis/vorbisfile.h>
|
||||
#include "meta.h"
|
||||
#include "ogg_vorbis_streamfile.h"
|
||||
|
||||
#define OGG_DEFAULT_BITSTREAM 0
|
||||
|
||||
|
@ -110,64 +111,12 @@ static void psychic_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb
|
|||
size_t bytes_read = size*nmemb;
|
||||
int i;
|
||||
|
||||
/* bytes add 0x23 ('#') */
|
||||
/* bytes add 0x23 ('#') */ //todo incorrect, add changes every 0x64 bytes
|
||||
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->xor_value);
|
||||
|
||||
/* first "OggS" is changed and bytes are xor'd and nibble-swapped */
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
if (ov_streamfile->offset+i < 0x04) {
|
||||
((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 */
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
((uint8_t*)ptr)[i] ^= key[(ov_streamfile->offset + i) % 16];
|
||||
}
|
||||
}
|
||||
|
||||
static void l2sd_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
||||
size_t bytes_read = size*nmemb;
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
int i;
|
||||
char *header_id = "OggS";
|
||||
|
||||
/* first "OggS" is changed */
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
if (ov_streamfile->offset+i < 0x04) {
|
||||
((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4];
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void rpgmvo_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
||||
static const uint8_t header[16] = { /* OggS, packet type, granule, stream id(empty) */
|
||||
0x4F,0x67,0x67,0x53,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
|
||||
|
@ -191,89 +140,6 @@ static void rpgmvo_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb,
|
|||
}
|
||||
}
|
||||
|
||||
static void eno_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;
|
||||
|
||||
/* bytes are xor'd */
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
((uint8_t*)ptr)[i] ^= (uint8_t)ov_streamfile->xor_value;
|
||||
}
|
||||
}
|
||||
|
||||
static void ys8_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;
|
||||
|
||||
/* bytes are xor'd and nibble-swapped */
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
uint8_t val = ((uint8_t*)ptr)[i] ^ ov_streamfile->xor_value;
|
||||
((uint8_t*)ptr)[i] = ((val << 4) & 0xf0) | ((val >> 4) & 0x0f);
|
||||
}
|
||||
}
|
||||
|
||||
static void gwm_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;
|
||||
|
||||
/* bytes are xor'd */
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
((uint8_t*)ptr)[i] ^= (uint8_t)ov_streamfile->xor_value;
|
||||
}
|
||||
}
|
||||
|
||||
static void mus_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
||||
static const uint8_t key[16] = {
|
||||
0x21,0x4D,0x6F,0x01,0x20,0x4C,0x6E,0x02,0x1F,0x4B,0x6D,0x03,0x20,0x4C,0x6E,0x02
|
||||
};
|
||||
|
||||
size_t bytes_read = size*nmemb;
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
int i;
|
||||
char *header_id = "OggS";
|
||||
|
||||
/* first "OggS" is changed and bytes are xor'd */
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
if (ov_streamfile->offset+i < 0x04) { /* if decrypted gives "Mus " */
|
||||
((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4];
|
||||
}
|
||||
else {
|
||||
((uint8_t*)ptr)[i] ^= key[(ov_streamfile->offset + i) % sizeof(key)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void lse_add_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;
|
||||
|
||||
/* bytes are xor'd */
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
int key = (uint8_t)ov_streamfile->xor_value + ((ov_streamfile->offset + i) % 256);
|
||||
((uint8_t*)ptr)[i] ^= key;
|
||||
}
|
||||
}
|
||||
|
||||
static void lse_ff_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, void *datasource) {
|
||||
size_t bytes_read = size*nmemb;
|
||||
ogg_vorbis_streamfile * const ov_streamfile = datasource;
|
||||
int i;
|
||||
char *header_id = "OggS";
|
||||
|
||||
/* first "OggS" is changed and bytes are xor'd */
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
if (ov_streamfile->offset+i < 0x04) {
|
||||
((uint8_t*)ptr)[i] = (uint8_t)header_id[(ov_streamfile->offset + i) % 4];
|
||||
}
|
||||
else {
|
||||
((uint8_t*)ptr)[i] ^= 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const uint32_t xiph_mappings[] = {
|
||||
0,
|
||||
|
@ -290,6 +156,9 @@ static const uint32_t xiph_mappings[] = {
|
|||
|
||||
/* Ogg Vorbis, by way of libvorbisfile; may contain loop comments */
|
||||
VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE *temp_streamFile = NULL;
|
||||
ogg_vorbis_io_config_data cfg = {0};
|
||||
ogg_vorbis_meta_info_t ovmi = {0};
|
||||
off_t start_offset = 0;
|
||||
|
||||
|
@ -320,7 +189,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
|||
is_kovs = 1;
|
||||
} else if (check_extensions(streamFile,"sngw")) { /* .sngw: Capcom [Devil May Cry 4 SE (PC), Biohazard 6 (PC)] */
|
||||
is_sngw = 1;
|
||||
} else if (check_extensions(streamFile,"isd")) { /* .isd: Azure Striker Gunvolt (PC) */
|
||||
} else if (check_extensions(streamFile,"isd")) { /* .isd: Inti Creates PC games */
|
||||
is_isd = 1;
|
||||
} else if (check_extensions(streamFile,"rpgmvo")) { /* .rpgmvo: RPG Maker MV games (PC) */
|
||||
is_rpgmvo = 1;
|
||||
|
@ -341,14 +210,21 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
|||
ovmi.decryption_callback = psychic_ogg_decryption_callback;
|
||||
ovmi.meta_type = meta_OGG_encrypted;
|
||||
}
|
||||
else if (read_32bitBE(0x00,streamFile) == 0x4C325344) { /* "L2SD" [Lineage II Chronicle 4 (PC)] */
|
||||
ovmi.decryption_callback = l2sd_ogg_decryption_callback;
|
||||
ovmi.meta_type = meta_OGG_encrypted;
|
||||
else if (read_32bitBE(0x00,streamFile) == 0x4C325344) { /* "L2SD" instead of "OggS" [Lineage II Chronicle 4 (PC)] */
|
||||
cfg.is_header_swap = 1;
|
||||
cfg.is_encrypted = 1;
|
||||
}
|
||||
else if (read_32bitBE(0x00,streamFile) == 0x048686C5) { /* "OggS" XOR'ed + bitswapped [Ys VIII (PC)] */
|
||||
ovmi.xor_value = 0xF0;
|
||||
ovmi.decryption_callback = ys8_ogg_decryption_callback;
|
||||
ovmi.meta_type = meta_OGG_encrypted;
|
||||
cfg.key[0] = 0xF0;
|
||||
cfg.key_len = 1;
|
||||
cfg.is_nibble_swap = 1;
|
||||
cfg.is_encrypted = 1;
|
||||
|
||||
}
|
||||
else if (read_32bitBE(0x00,streamFile) == 0x00000000 && /* null instead of "OggS" [Yuppie Psycho (PC)] */
|
||||
read_32bitBE(0x3a,streamFile) == 0x4F676753) {
|
||||
cfg.is_header_swap = 1;
|
||||
cfg.is_encrypted = 1;
|
||||
}
|
||||
else if (read_32bitBE(0x00,streamFile) == 0x4f676753) { /* "OggS" (standard) */
|
||||
ovmi.meta_type = meta_OGG_VORBIS;
|
||||
|
@ -365,7 +241,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
|||
ovmi.meta_type = meta_OGG_encrypted;
|
||||
}
|
||||
|
||||
if (is_kovs) { /* Koei Tecmo PC games] */
|
||||
if (is_kovs) { /* Koei Tecmo PC games */
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4b4f5653) { /* "KOVS" */
|
||||
goto fail;
|
||||
}
|
||||
|
@ -379,24 +255,107 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
|||
|
||||
if (is_sngw) { /* [Capcom's MT Framework PC games] */
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" (optionally encrypted) */
|
||||
ovmi.xor_value = read_32bitBE(0x00,streamFile);
|
||||
ovmi.decryption_callback = sngw_ogg_decryption_callback;
|
||||
cfg.key_len = read_streamfile(cfg.key, 0x00, 0x04, streamFile);
|
||||
cfg.is_header_swap = 1;
|
||||
cfg.is_nibble_swap = 1;
|
||||
cfg.is_encrypted = 1;
|
||||
}
|
||||
|
||||
ovmi.disable_reordering = 1; /* must be an MT Framework thing */
|
||||
ovmi.meta_type = meta_OGG_encrypted;
|
||||
}
|
||||
|
||||
if (is_isd) { /* [Gunvolt (PC)] */
|
||||
ovmi.decryption_callback = isd_ogg_decryption_callback;
|
||||
ovmi.meta_type = meta_OGG_encrypted;
|
||||
if (is_isd) { /* Inti Creates PC games */
|
||||
const char *isl_name = NULL;
|
||||
|
||||
//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)
|
||||
/* check various encrypted "OggS" values */
|
||||
if (read_32bitBE(0x00,streamFile) == 0xAF678753) { /* Azure Striker Gunvolt (PC) */
|
||||
static const uint8_t isd_gv_key[16] = {
|
||||
0xe0,0x00,0xe0,0x00,0xa0,0x00,0x00,0x00,0xe0,0x00,0xe0,0x80,0x40,0x40,0x40,0x00
|
||||
};
|
||||
cfg.key_len = sizeof(isd_gv_key);
|
||||
memcpy(cfg.key, isd_gv_key, cfg.key_len);
|
||||
isl_name = "GV_steam.isl";
|
||||
}
|
||||
else if (read_32bitBE(0x00,streamFile) == 0x0FE787D3) { /* Mighty Gunvolt (PC) */
|
||||
static const uint8_t isd_mgv_key[120] = {
|
||||
0x40,0x80,0xE0,0x80,0x40,0x40,0xA0,0x00,0xA0,0x40,0x00,0x80,0x00,0x40,0xA0,0x00,
|
||||
0xC0,0x40,0xE0,0x00,0x60,0x40,0x80,0x00,0xA0,0x00,0xE0,0x00,0x60,0x40,0xC0,0x00,
|
||||
0xA0,0x40,0xC0,0x80,0xE0,0x00,0x60,0x00,0x00,0x40,0x00,0x80,0xE0,0x80,0x40,0x00,
|
||||
0xA0,0x80,0xA0,0x80,0x80,0xC0,0x60,0x00,0xA0,0x00,0xA0,0x80,0x40,0x80,0x60,0x00,
|
||||
0x40,0xC0,0x20,0x00,0x20,0xC0,0x00,0x00,0x00,0xC0,0x20,0x00,0xC0,0xC0,0x60,0x00,
|
||||
0xE0,0xC0,0x80,0x80,0x20,0x00,0x60,0x00,0xE0,0xC0,0xC0,0x00,0x20,0xC0,0xC0,0x00,
|
||||
0x60,0x00,0xE0,0x80,0x00,0xC0,0x00,0x00,0x60,0x80,0x40,0x80,0x20,0x80,0x20,0x00,
|
||||
0x80,0x40,0xE0,0x00,0x20,0x00,0x20,0x00,
|
||||
};
|
||||
cfg.key_len = sizeof(isd_mgv_key);
|
||||
memcpy(cfg.key, isd_mgv_key, cfg.key_len);
|
||||
isl_name = "MGV_steam.isl";
|
||||
}
|
||||
else if (read_32bitBE(0x00,streamFile) == 0x0FA74753) { /* Blaster Master Zero (PC) */
|
||||
static const uint8_t isd_bmz_key[120] = {
|
||||
0x40,0xC0,0x20,0x00,0x40,0xC0,0xC0,0x00,0x00,0x80,0xE0,0x80,0x80,0x40,0x20,0x00,
|
||||
0x60,0xC0,0xC0,0x00,0xA0,0x80,0x60,0x00,0x40,0x40,0x20,0x00,0x60,0x40,0xC0,0x00,
|
||||
0x60,0x80,0xC0,0x80,0x40,0xC0,0x00,0x00,0xA0,0xC0,0x80,0x80,0x60,0x80,0xA0,0x00,
|
||||
0x40,0x80,0x60,0x00,0x20,0x00,0xC0,0x00,0x60,0x00,0xA0,0x80,0x40,0x40,0xA0,0x00,
|
||||
0x40,0x40,0xC0,0x80,0x00,0x80,0x60,0x00,0x80,0xC0,0xA0,0x00,0xE0,0x40,0xC0,0x00,
|
||||
0x20,0x80,0xE0,0x00,0x40,0xC0,0xA0,0x00,0xE0,0xC0,0xC0,0x80,0xE0,0x80,0xC0,0x00,
|
||||
0x40,0x40,0x00,0x00,0x20,0x40,0x80,0x00,0xE0,0x80,0x20,0x80,0x40,0x80,0xE0,0x00,
|
||||
0xA0,0x00,0xC0,0x80,0xE0,0x00,0x20,0x00
|
||||
};
|
||||
cfg.key_len = sizeof(isd_bmz_key);
|
||||
memcpy(cfg.key, isd_bmz_key, cfg.key_len);
|
||||
isl_name = "output.isl";
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cfg.is_encrypted = 1;
|
||||
|
||||
/* .isd have companion files in the prev folder:
|
||||
* - .ish: constant id/names (not always)
|
||||
* - .isf: format table, ordered like file id/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)
|
||||
* - .isl: looping table (encrypted like the files) */
|
||||
if (isl_name) {
|
||||
STREAMFILE *islFile = NULL;
|
||||
|
||||
//todo could try in ../(file) first since that's how the .isl is stored
|
||||
islFile = open_streamfile_by_filename(streamFile, isl_name);
|
||||
if (islFile) {
|
||||
STREAMFILE *dec_sf = NULL;
|
||||
|
||||
dec_sf = setup_ogg_vorbis_streamfile(islFile, cfg);
|
||||
if (dec_sf) {
|
||||
off_t loop_offset;
|
||||
char basename[PATH_LIMIT];
|
||||
|
||||
/* has a bunch of tables then a list with file names without extension and loops */
|
||||
loop_offset = read_32bitLE(0x18, dec_sf);
|
||||
get_streamfile_basename(streamFile, basename, sizeof(basename));
|
||||
|
||||
while (loop_offset < get_streamfile_size(dec_sf)) {
|
||||
char testname[0x20];
|
||||
|
||||
read_string(testname, sizeof(testname), loop_offset+0x2c, dec_sf);
|
||||
if (strcmp(basename, testname) == 0) {
|
||||
ovmi.loop_start = read_32bitLE(loop_offset+0x1c, dec_sf);
|
||||
ovmi.loop_end = read_32bitLE(loop_offset+0x20, dec_sf);
|
||||
ovmi.loop_end_found = 1;
|
||||
ovmi.loop_flag = (ovmi.loop_end != 0);
|
||||
break;
|
||||
}
|
||||
|
||||
loop_offset += 0x50;
|
||||
}
|
||||
|
||||
close_streamfile(dec_sf);
|
||||
}
|
||||
|
||||
close_streamfile(islFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_rpgmvo) { /* [RPG Maker MV (PC)] */
|
||||
|
@ -411,42 +370,63 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
|
|||
}
|
||||
|
||||
if (is_eno) { /* [Metronomicon (PC)] */
|
||||
/* first byte probably derives into xor key, but this works too */
|
||||
ovmi.xor_value = read_8bit(0x05,streamFile); /* always zero = easy key */
|
||||
ovmi.decryption_callback = eno_ogg_decryption_callback;
|
||||
ovmi.meta_type = meta_OGG_encrypted;
|
||||
|
||||
start_offset = 0x01;
|
||||
/* first byte probably derives into key, but this works too */
|
||||
cfg.key[0] = (uint8_t)read_8bit(0x05,streamFile); /* regular ogg have a zero at this offset = easy key */;
|
||||
cfg.key_len = 1;
|
||||
cfg.is_encrypted = 1;
|
||||
start_offset = 0x01; /* "OggS" starts after key-thing */
|
||||
}
|
||||
|
||||
if (is_gwm) { /* [Adagio: Cloudburst (PC)] */
|
||||
ovmi.xor_value = 0x5D;
|
||||
ovmi.decryption_callback = gwm_ogg_decryption_callback;
|
||||
ovmi.meta_type = meta_OGG_encrypted;
|
||||
cfg.key[0] = 0x5D;
|
||||
cfg.key_len = 1;
|
||||
cfg.is_encrypted = 1;
|
||||
}
|
||||
|
||||
if (is_mus) { /* [Redux - Dark Matters (PC)] */
|
||||
ovmi.decryption_callback = mus_ogg_decryption_callback;
|
||||
ovmi.meta_type = meta_OGG_encrypted;
|
||||
static const uint8_t mus_key[16] = {
|
||||
0x21,0x4D,0x6F,0x01,0x20,0x4C,0x6E,0x02,0x1F,0x4B,0x6D,0x03,0x20,0x4C,0x6E,0x02
|
||||
};
|
||||
cfg.key_len = sizeof(mus_key);
|
||||
memcpy(cfg.key, mus_key, cfg.key_len);
|
||||
cfg.is_header_swap = 1; /* decrypted header gives "Mus " */
|
||||
cfg.is_encrypted = 1;
|
||||
}
|
||||
|
||||
if (is_lse) { /* [Nippon Ichi PC games] */
|
||||
if (read_32bitBE(0x00,streamFile) == 0xFFFFFFFF) { /* [Operation Abyss: New Tokyo Legacy (PC)] */
|
||||
ovmi.decryption_callback = lse_ff_ogg_decryption_callback;
|
||||
ovmi.meta_type = meta_OGG_encrypted;
|
||||
cfg.key[0] = 0xFF;
|
||||
cfg.key_len = 1;
|
||||
cfg.is_header_swap = 1;
|
||||
cfg.is_encrypted = 1;
|
||||
}
|
||||
else { /* [Operation Babel: New Tokyo Legacy (PC), Labyrinth of Refrain: Coven of Dusk (PC)] */
|
||||
ovmi.decryption_callback = lse_add_ogg_decryption_callback;
|
||||
ovmi.xor_value = (uint8_t)read_8bit(0x04,streamFile) - 0x04;
|
||||
int i;
|
||||
/* found at file_size-1 but this works too (same key for most files but can vary) */
|
||||
uint8_t base_key = (uint8_t)read_8bit(0x04,streamFile) - 0x04;
|
||||
|
||||
cfg.key_len = 256;
|
||||
for (i = 0; i < cfg.key_len; i++) {
|
||||
cfg.key[i] = (uint8_t)(base_key + i);
|
||||
}
|
||||
cfg.is_encrypted = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg.is_encrypted) {
|
||||
ovmi.meta_type = meta_OGG_encrypted;
|
||||
/* key is found at file_size-1 but this works too (same key for most files but can vary) */
|
||||
}
|
||||
|
||||
temp_streamFile = setup_ogg_vorbis_streamfile(streamFile, cfg);
|
||||
if (!temp_streamFile) goto fail;
|
||||
}
|
||||
|
||||
vgmstream = init_vgmstream_ogg_vorbis_callbacks(temp_streamFile != NULL ? temp_streamFile : streamFile, NULL, start_offset, &ovmi);
|
||||
|
||||
return init_vgmstream_ogg_vorbis_callbacks(streamFile, NULL, start_offset, &ovmi);
|
||||
close_streamfile(temp_streamFile);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -528,6 +508,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb
|
|||
ovf = &data->ogg_vorbis_file;
|
||||
}
|
||||
|
||||
//todo could set bitstreams as subsongs?
|
||||
/* get info from bitstream 0 */
|
||||
data->bitstream = OGG_DEFAULT_BITSTREAM;
|
||||
vi = ov_info(ovf,OGG_DEFAULT_BITSTREAM);
|
||||
|
@ -536,7 +517,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb
|
|||
data->disable_reordering = ovmi->disable_reordering;
|
||||
|
||||
/* search for loop comments */
|
||||
{
|
||||
{//todo ignore if loop flag already set?
|
||||
int i;
|
||||
vorbis_comment *comment = ov_comment(ovf,OGG_DEFAULT_BITSTREAM);
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
#ifndef _OGG_VORBIS_STREAMFILE_H_
|
||||
#define _OGG_VORBIS_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
int is_encrypted;
|
||||
uint8_t key[0x100];
|
||||
size_t key_len;
|
||||
int is_nibble_swap;
|
||||
int is_header_swap;
|
||||
} ogg_vorbis_io_config_data;
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
ogg_vorbis_io_config_data cfg;
|
||||
} ogg_vorbis_io_data;
|
||||
|
||||
|
||||
static size_t ogg_vorbis_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, ogg_vorbis_io_data* data) {
|
||||
size_t bytes_read;
|
||||
int i;
|
||||
static const uint8_t header_swap[4] = { 0x4F,0x67,0x67,0x53 }; /* "OggS" */
|
||||
static const size_t header_size = 0x04;
|
||||
|
||||
bytes_read = streamfile->read(streamfile, dest, offset, length);
|
||||
|
||||
if (data->cfg.is_encrypted) {
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
if (data->cfg.is_header_swap && (offset + i) < header_size) {
|
||||
dest[i] = header_swap[(offset + i) % header_size];
|
||||
}
|
||||
else {
|
||||
if (!data->cfg.key_len && !data->cfg.is_nibble_swap)
|
||||
break;
|
||||
if (data->cfg.key_len)
|
||||
dest[i] ^= data->cfg.key[(offset + i) % data->cfg.key_len];
|
||||
if (data->cfg.is_nibble_swap)
|
||||
dest[i] = ((dest[i] << 4) & 0xf0) | ((dest[i] >> 4) & 0x0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
//todo maybe use generic decryption streamfile
|
||||
static STREAMFILE* setup_ogg_vorbis_streamfile(STREAMFILE *streamFile, ogg_vorbis_io_config_data cfg) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
ogg_vorbis_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(ogg_vorbis_io_data);
|
||||
|
||||
/* setup decryption */
|
||||
io_data.cfg = cfg; /* memcpy */
|
||||
|
||||
|
||||
/* setup custom streamfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
//todo extension .ogg?
|
||||
|
||||
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, ogg_vorbis_io_read,NULL);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#endif /* _OGG_VORBIS_STREAMFILE_H_ */
|
|
@ -19,7 +19,8 @@ static VGMSTREAM * init_vgmstream_opus(STREAMFILE *streamFile, meta_t meta_type,
|
|||
channel_count = read_8bit(offset + 0x09, streamFile);
|
||||
/* 0x0a: packet size if CBR, 0 if VBR */
|
||||
data_offset = offset + read_32bitLE(offset + 0x10, streamFile);
|
||||
skip = read_32bitLE(offset + 0x1c, streamFile);
|
||||
skip = read_16bitLE(offset + 0x1c, streamFile);
|
||||
/* 0x1e: ? (seen in Lego Movie 2 (Switch)) */
|
||||
|
||||
if ((uint32_t)read_32bitLE(data_offset, streamFile) != 0x80000004)
|
||||
goto fail;
|
||||
|
|
|
@ -154,6 +154,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
|
|||
|
||||
ovmi.meta_type = meta_SQEX_SCD;
|
||||
ovmi.total_subsongs = total_subsongs;
|
||||
ovmi.disable_reordering = 1; /* already ordered */
|
||||
/* loop values are in bytes, let init_vgmstream_ogg_vorbis find loop comments instead */
|
||||
|
||||
ogg_version = read_8bit(extradata_offset + 0x00, streamFile);
|
||||
|
|
|
@ -70,6 +70,7 @@ typedef struct {
|
|||
uint32_t skip_samples;
|
||||
|
||||
uint32_t loop_flag;
|
||||
uint32_t loop_behavior;
|
||||
int loop_flag_set;
|
||||
int loop_flag_auto;
|
||||
|
||||
|
@ -105,6 +106,11 @@ typedef struct {
|
|||
int chunk_size_set;
|
||||
int chunk_count_set;
|
||||
|
||||
uint32_t base_offset;
|
||||
|
||||
uint32_t name_values[16];
|
||||
int name_values_count;
|
||||
|
||||
/* original STREAMFILE and its type (may be an unsupported "base" file or a .txth) */
|
||||
STREAMFILE *streamFile;
|
||||
int streamfile_is_txth;
|
||||
|
@ -565,6 +571,19 @@ static VGMSTREAM *init_subfile(txth_header * txth) {
|
|||
//todo: other combos with subsongs + subfile?
|
||||
|
||||
|
||||
/* load some fields for possible calcs */
|
||||
if (!txth->channels)
|
||||
txth->channels = vgmstream->channels;
|
||||
if (!txth->sample_rate)
|
||||
txth->sample_rate = vgmstream->sample_rate;
|
||||
if (!txth->interleave)
|
||||
txth->interleave = vgmstream->interleave_block_size;
|
||||
if (!txth->interleave_last)
|
||||
txth->interleave_last = vgmstream->interleave_last_block_size;
|
||||
//if (!txth->loop_flag) //?
|
||||
// txth->loop_flag = vgmstream->loop_flag;
|
||||
|
||||
|
||||
close_streamfile(streamSubfile);
|
||||
return vgmstream;
|
||||
|
||||
|
@ -695,8 +714,8 @@ static int parse_keyval(STREAMFILE * streamFile, txth_header * txth, const char
|
|||
static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value);
|
||||
static int parse_string(STREAMFILE * streamFile, txth_header * txth, const char * val, char * str);
|
||||
static int parse_coef_table(STREAMFILE * streamFile, txth_header * txth, const char * val, uint8_t * out_value, size_t out_size);
|
||||
static int parse_name_table(txth_header * txth, char * val);
|
||||
static int is_string(const char * val, const char * cmp);
|
||||
static int is_substring(const char * val, const char * cmp);
|
||||
static int get_bytes_to_samples(txth_header * txth, uint32_t bytes);
|
||||
static int get_padding_size(txth_header * txth, int discard_empty);
|
||||
|
||||
|
@ -979,11 +998,32 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char
|
|||
else {
|
||||
if (!parse_num(txth->streamHead,txth,val, &txth->loop_flag)) goto fail;
|
||||
txth->loop_flag_set = 1;
|
||||
if (txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) { /* normally -1 = no loop */
|
||||
|
||||
if (txth->loop_behavior == 0) {
|
||||
if ((txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF) )
|
||||
txth->loop_flag = 0;
|
||||
|
||||
}
|
||||
else if (txth->loop_behavior == 1) {
|
||||
if (txth->loop_flag == 0xFF || txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF)
|
||||
txth->loop_flag = 1;
|
||||
}
|
||||
else if (txth->loop_behavior == 2) {
|
||||
if (txth->loop_flag == 0xFF || txth->loop_flag == 0xFFFF || txth->loop_flag == 0xFFFFFFFF)
|
||||
txth->loop_flag = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (is_string(key,"loop_behavior")) {
|
||||
if (is_string(val, "default"))
|
||||
txth->loop_behavior = 0;
|
||||
else if (is_string(val, "negative"))
|
||||
txth->loop_behavior = 1;
|
||||
else if (is_string(val, "positive"))
|
||||
txth->loop_behavior = 2;
|
||||
else
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* COEFS */
|
||||
else if (is_string(key,"coef_offset")) {
|
||||
|
@ -1139,6 +1179,17 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char
|
|||
set_body_chunk(txth);
|
||||
}
|
||||
|
||||
/* BASE OFFSET */
|
||||
else if (is_string(key,"base_offset")) {
|
||||
if (!parse_num(txth->streamHead,txth,val, &txth->base_offset)) goto fail;
|
||||
}
|
||||
|
||||
/* NAME TABLE */
|
||||
else if (is_string(key,"name_table")) {
|
||||
if (!parse_name_table(txth,val)) goto fail;
|
||||
}
|
||||
|
||||
|
||||
/* DEFAULT */
|
||||
else {
|
||||
VGM_LOG("TXTH: unknown key=%s, val=%s\n", key,val);
|
||||
|
@ -1152,8 +1203,28 @@ fail:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int is_substring(const char * val, const char * cmp, int inline_field) {
|
||||
char chr;
|
||||
int len = strlen(cmp);
|
||||
/* "val" must contain "cmp" entirely */
|
||||
if (strncmp(val, cmp, len) != 0)
|
||||
return 0;
|
||||
|
||||
chr = val[len];
|
||||
|
||||
/* "val" can end with math for inline fields (like interleave*0x10) */
|
||||
if (inline_field && (chr == '+' || chr == '-' || chr == '*' || chr == '/'))
|
||||
return len;
|
||||
|
||||
/* otherwise "val" ends in space or eof (to tell "interleave" and "interleave_last" apart) */
|
||||
if (chr != '\0' && chr != ' ')
|
||||
return 0;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int is_string(const char * val, const char * cmp) {
|
||||
int len = is_substring(val, cmp);
|
||||
int len = is_substring(val, cmp, 0);
|
||||
if (!len) return 0;
|
||||
|
||||
/* also test that after string there aren't other values
|
||||
|
@ -1166,19 +1237,8 @@ static int is_string(const char * val, const char * cmp) {
|
|||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int is_substring(const char * val, const char * cmp) {
|
||||
int len = strlen(cmp);
|
||||
if (strncmp(val, cmp, len) != 0)
|
||||
return 0;
|
||||
|
||||
/* string in val must be a full word (end with null or space) to
|
||||
* avoid mistaking stuff like "interleave" with "interleave_last"
|
||||
* (could also check , except when used for math */
|
||||
if (val[len] != '\0' && val[len] != ' ')
|
||||
return 0;
|
||||
|
||||
return len;
|
||||
static int is_string_field(const char * val, const char * cmp) {
|
||||
return is_substring(val, cmp, 1);
|
||||
}
|
||||
|
||||
static int parse_string(STREAMFILE * streamFile, txth_header * txth, const char * val, char * str) {
|
||||
|
@ -1218,6 +1278,113 @@ fail:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int parse_name_table(txth_header * txth, char * name_list) {
|
||||
STREAMFILE *nameFile = NULL;
|
||||
off_t txt_offset, file_size;
|
||||
char filename[PATH_LIMIT];
|
||||
char basename[PATH_LIMIT];
|
||||
|
||||
/* just in case */
|
||||
if (txth->streamfile_is_txth || !txth->streamText || !txth->streamFile)
|
||||
goto fail;
|
||||
|
||||
/* trim name_list just in case */
|
||||
{
|
||||
int name_list_len = strlen(name_list);
|
||||
int i;
|
||||
for (i = name_list_len - 1; i >= 0; i--) {
|
||||
if (name_list[i] != ' ')
|
||||
break;
|
||||
name_list[i] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
//;VGM_LOG("TXTH: name_list2='%s'\n", name_list);
|
||||
|
||||
/* open companion file near .txth */
|
||||
nameFile = open_streamfile_by_filename(txth->streamText, name_list);
|
||||
if (!nameFile) goto fail;
|
||||
|
||||
get_streamfile_filename(txth->streamFile, filename, sizeof(filename));
|
||||
get_streamfile_basename(txth->streamFile, basename, sizeof(basename));
|
||||
//;VGM_LOG("TXTH: filename=%s, basename=%s\n", filename, basename);
|
||||
|
||||
txt_offset = 0x00;
|
||||
file_size = get_streamfile_size(nameFile);
|
||||
|
||||
/* skip BOM if needed */
|
||||
if ((uint16_t)read_16bitLE(0x00, nameFile) == 0xFFFE ||
|
||||
(uint16_t)read_16bitLE(0x00, nameFile) == 0xFEFF) {
|
||||
txt_offset = 0x02;
|
||||
}
|
||||
else if (((uint32_t)read_32bitBE(0x00, nameFile) & 0xFFFFFF00) == 0xEFBBBF00) {
|
||||
txt_offset = 0x03;
|
||||
}
|
||||
|
||||
/* in case of repeated name_lists */
|
||||
memset(txth->name_values, 0, sizeof(txth->name_values));
|
||||
txth->name_values_count = 0;
|
||||
|
||||
/* read lines and find target filename, format is (filename): value1, ... valueN */
|
||||
while (txt_offset < file_size) {
|
||||
char line[TXT_LINE_MAX] = {0};
|
||||
char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0};
|
||||
int ok, bytes_read, line_done;
|
||||
|
||||
bytes_read = get_streamfile_text_line(TXT_LINE_MAX,line, txt_offset,nameFile, &line_done);
|
||||
if (!line_done) goto fail;
|
||||
//;VGM_LOG("TXTH: line=%s\n",line);
|
||||
|
||||
txt_offset += bytes_read;
|
||||
|
||||
/* get key/val (ignores lead spaces, stops at space/comment/separator) */
|
||||
ok = sscanf(line, " %[^ \t#:] : %[^\t#\r\n] ", key,val);
|
||||
if (ok != 2) { /* ignore line if no key=val (comment or garbage) */
|
||||
/* try again with " (empty): (val)) */
|
||||
key[0] = '\0';
|
||||
ok = sscanf(line, " : %[^\t#\r\n] ", val);
|
||||
if (ok != 1)
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
//;VGM_LOG("TXTH: compare name '%s'\n", key);
|
||||
/* parse values if key (name) matches default ("") or filename with/without extension */
|
||||
if (key[0]=='\0' || strcmpi(key, filename)==0 || strcmpi(key, basename)==0) {
|
||||
int n;
|
||||
char subval[TXT_LINE_MAX];
|
||||
const char *current = val;
|
||||
|
||||
while (current[0] != '\0') {
|
||||
ok = sscanf(current, " %[^\t#\r\n,]%n ", subval, &n);
|
||||
if (ok != 1)
|
||||
goto fail;
|
||||
|
||||
current += n;
|
||||
if (current[0] == ',')
|
||||
current++;
|
||||
|
||||
if (!parse_num(txth->streamHead,txth,subval, &txth->name_values[txth->name_values_count])) goto fail;
|
||||
txth->name_values_count++;
|
||||
if (txth->name_values_count >= 16) /* surely nobody needs that many */
|
||||
goto fail;
|
||||
}
|
||||
|
||||
//;VGM_LOG("TXTH: found name '%s'\n", key);
|
||||
break; /* target found */
|
||||
}
|
||||
}
|
||||
|
||||
/* ignore if name is not actually found (values will return 0) */
|
||||
|
||||
close_streamfile(nameFile);
|
||||
return 1;
|
||||
fail:
|
||||
close_streamfile(nameFile);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value) {
|
||||
/* out_value can be these, save before modifying */
|
||||
uint32_t value_mul = txth->value_mul;
|
||||
|
@ -1278,6 +1445,9 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v
|
|||
if (sscanf(val, hex ? "@%x%n" : "@%u%n", &offset, &n) != 1) goto fail;
|
||||
}
|
||||
|
||||
/* adjust offset */
|
||||
offset += txth->base_offset;
|
||||
|
||||
if (/*offset < 0 ||*/ offset > get_streamfile_size(streamFile))
|
||||
goto fail;
|
||||
|
||||
|
@ -1290,7 +1460,7 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v
|
|||
offset = offset + subsong_offset * (txth->target_subsong - 1);
|
||||
|
||||
switch(size) {
|
||||
case 1: value = read_8bit(offset,streamFile); break;
|
||||
case 1: value = (uint8_t)read_8bit(offset,streamFile); break;
|
||||
case 2: value = big_endian ? (uint16_t)read_16bitBE(offset,streamFile) : (uint16_t)read_16bitLE(offset,streamFile); break;
|
||||
case 3: value = (big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile)) & 0x00FFFFFF; break;
|
||||
case 4: value = big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile); break;
|
||||
|
@ -1306,17 +1476,35 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v
|
|||
value_read = 1;
|
||||
}
|
||||
else { /* known field */
|
||||
if ((n = is_substring(val,"interleave"))) value = txth->interleave;
|
||||
else if ((n = is_substring(val,"interleave_last"))) value = txth->interleave_last;
|
||||
else if ((n = is_substring(val,"channels"))) value = txth->channels;
|
||||
else if ((n = is_substring(val,"sample_rate"))) value = txth->sample_rate;
|
||||
else if ((n = is_substring(val,"start_offset"))) value = txth->start_offset;
|
||||
else if ((n = is_substring(val,"data_size"))) value = txth->data_size;
|
||||
else if ((n = is_substring(val,"num_samples"))) value = txth->num_samples;
|
||||
else if ((n = is_substring(val,"loop_start_sample"))) value = txth->loop_start_sample;
|
||||
else if ((n = is_substring(val,"loop_end_sample"))) value = txth->loop_end_sample;
|
||||
else if ((n = is_substring(val,"subsong_count"))) value = txth->subsong_count;
|
||||
else if ((n = is_substring(val,"subsong_offset"))) value = txth->subsong_offset;
|
||||
if ((n = is_string_field(val,"interleave"))) value = txth->interleave;
|
||||
else if ((n = is_string_field(val,"interleave_last"))) value = txth->interleave_last;
|
||||
else if ((n = is_string_field(val,"channels"))) value = txth->channels;
|
||||
else if ((n = is_string_field(val,"sample_rate"))) value = txth->sample_rate;
|
||||
else if ((n = is_string_field(val,"start_offset"))) value = txth->start_offset;
|
||||
else if ((n = is_string_field(val,"data_size"))) value = txth->data_size;
|
||||
else if ((n = is_string_field(val,"num_samples"))) value = txth->num_samples;
|
||||
else if ((n = is_string_field(val,"loop_start_sample"))) value = txth->loop_start_sample;
|
||||
else if ((n = is_string_field(val,"loop_end_sample"))) value = txth->loop_end_sample;
|
||||
else if ((n = is_string_field(val,"subsong_count"))) value = txth->subsong_count;
|
||||
else if ((n = is_string_field(val,"subsong_offset"))) value = txth->subsong_offset;
|
||||
//todo whatever, improve
|
||||
else if ((n = is_string_field(val,"name_value"))) value = txth->name_values[0];
|
||||
else if ((n = is_string_field(val,"name_value1"))) value = txth->name_values[0];
|
||||
else if ((n = is_string_field(val,"name_value2"))) value = txth->name_values[1];
|
||||
else if ((n = is_string_field(val,"name_value3"))) value = txth->name_values[2];
|
||||
else if ((n = is_string_field(val,"name_value4"))) value = txth->name_values[3];
|
||||
else if ((n = is_string_field(val,"name_value5"))) value = txth->name_values[4];
|
||||
else if ((n = is_string_field(val,"name_value6"))) value = txth->name_values[5];
|
||||
else if ((n = is_string_field(val,"name_value7"))) value = txth->name_values[6];
|
||||
else if ((n = is_string_field(val,"name_value8"))) value = txth->name_values[7];
|
||||
else if ((n = is_string_field(val,"name_value9"))) value = txth->name_values[8];
|
||||
else if ((n = is_string_field(val,"name_value10"))) value = txth->name_values[9];
|
||||
else if ((n = is_string_field(val,"name_value11"))) value = txth->name_values[10];
|
||||
else if ((n = is_string_field(val,"name_value12"))) value = txth->name_values[11];
|
||||
else if ((n = is_string_field(val,"name_value13"))) value = txth->name_values[12];
|
||||
else if ((n = is_string_field(val,"name_value14"))) value = txth->name_values[13];
|
||||
else if ((n = is_string_field(val,"name_value15"))) value = txth->name_values[14];
|
||||
else if ((n = is_string_field(val,"name_value16"))) value = txth->name_values[15];
|
||||
else goto fail;
|
||||
value_read = 1;
|
||||
}
|
||||
|
|
|
@ -160,11 +160,28 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) {
|
|||
if (xvag.layers > 1 && !(xvag.layers*1 == vgmstream->channels || xvag.layers*2 == vgmstream->channels)) goto fail;
|
||||
|
||||
/* "mpin": mpeg info */
|
||||
/* 0x00/04: mpeg version/layer? other: unknown or repeats of "fmat" */
|
||||
if (!find_chunk(streamFile, 0x6D70696E,first_offset,0, &chunk_offset,NULL, xvag.big_endian, 1)) /*"mpin"*/
|
||||
goto fail;
|
||||
|
||||
cfg.chunk_size = read_32bit(chunk_offset+0x1c,streamFile); /* fixed frame size */
|
||||
/* all layers/subsongs share the same config; not very useful but for posterity:
|
||||
* - 0x00: mpeg version
|
||||
* - 0x04: mpeg layer
|
||||
* - 0x08: bit rate
|
||||
* - 0x0c: sample rate
|
||||
* - 0x10: some version? (0x01-0x03)?
|
||||
* - 0x14: channels per stream?
|
||||
* - 0x18: channels per stream or total channels?
|
||||
* - 0x1c: fixed frame size (always CBR)
|
||||
* - 0x20: encoder delay (usually but not always 1201)
|
||||
* - 0x24: number of samples
|
||||
* - 0x28: some size?
|
||||
* - 0x2c: ? (0x02)
|
||||
* - 0x30: ? (0x00, 0x80)
|
||||
* - 0x34: data size
|
||||
* (rest is padding)
|
||||
* */
|
||||
cfg.chunk_size = read_32bit(chunk_offset+0x1c,streamFile);
|
||||
cfg.skip_samples = read_32bit(chunk_offset+0x20,streamFile);
|
||||
cfg.interleave = cfg.chunk_size * xvag.factor;
|
||||
|
||||
vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_XVAG, &cfg);
|
||||
|
|
|
@ -345,8 +345,10 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
|
|||
/* Oddworld OGG the data_size value is size of uncompressed bytes instead; DSP uses some id/config as value */
|
||||
if (xwb.codec != OGG && xwb.codec != DSP && xwb.codec != ATRAC9_RIFF) {
|
||||
/* some low-q rips don't remove padding, relax validation a bit */
|
||||
if (xwb.data_offset + xwb.data_size > get_streamfile_size(streamFile))
|
||||
if (xwb.data_offset + xwb.stream_size > get_streamfile_size(streamFile))
|
||||
goto fail;
|
||||
//if (xwb.data_offset + xwb.data_size > get_streamfile_size(streamFile)) /* badly split */
|
||||
// goto fail;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -480,6 +480,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
|
|||
init_vgmstream_xwma_konami,
|
||||
init_vgmstream_9tav,
|
||||
init_vgmstream_fsb5_fev_bank,
|
||||
init_vgmstream_bwav,
|
||||
|
||||
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
|
||||
init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */
|
||||
|
|
|
@ -742,6 +742,7 @@ typedef enum {
|
|||
meta_MSF_KONAMI,
|
||||
meta_XWMA_KONAMI,
|
||||
meta_9TAV,
|
||||
meta_BWAV,
|
||||
|
||||
} meta_t;
|
||||
|
||||
|
@ -1042,6 +1043,7 @@ typedef struct {
|
|||
int interleave; /* size of stream interleave */
|
||||
int encryption; /* encryption mode */
|
||||
int big_endian;
|
||||
int skip_samples;
|
||||
/* for AHX */
|
||||
int cri_type;
|
||||
uint16_t cri_key1;
|
||||
|
|
Loading…
Reference in New Issue