Merge pull request #1675 from bnnm/fsb-api-etc

- Add .oga/ogs/ogv Ogg extensions
- Fix some FSB3 [Rise of the Argonauts (PC)]
- Add .ps3 FSB5 [Guacamelee! (PS3)]
- Fix rare .at3 [Up (PSP)]
- Fix PCM16LE .caf [Katamari Amore (iOS)]
- Reject wonky FSB MPEG/AT3
- Add HCA key
- Add .xhd+xbd [Red Dead Revolver (Xbox), Spy Fiction 2 (Xbox)]
- Fix minor .wv2 issues
- cleanup
This commit is contained in:
bnnm 2025-02-03 00:07:15 +01:00 committed by GitHub
commit 70d4ac8821
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 1219 additions and 791 deletions

View File

@ -1,5 +1,4 @@
#include "../src/libvgmstream.h"
#if LIBVGMSTREAM_ENABLE
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
@ -59,7 +58,7 @@ static int api_example(const char* infile) {
libvgmstream_options_t opt = {
.libsf = get_streamfile(infile)
};
err = libvgmstream_open_song(lib, &opt);
err = libvgmstream_open_stream(lib, &opt);
// external SF is not needed after _open
libstreamfile_close(opt.libsf);
@ -137,7 +136,7 @@ static int api_example(const char* infile) {
printf("\n");
// close current streamfile before opening new ones, optional
//libvgmstream_close_song(lib);
//libvgmstream_close_stream(lib);
// process done
libvgmstream_free(lib);
@ -403,4 +402,3 @@ int main(int argc, char** argv) {
return 0;
}
#endif

View File

@ -3,8 +3,23 @@
/* What is this crap, you may wonder? For probably non-existant use cases Jansson was added to write JSON info,
* but external libs are a pain to maintain. For now this glorified string joiner replaces it.
*
*
* On incorrect usage or small buf it'll create invalid JSON because who cares, try-parse-catch as usual.
*
* Example usage:
* char buf[MAX_JSON_SIZE]; // fixed size, meaning we need to know the approximate max
* vjson_t j = {0}; // alloc this or the buf if needed
* vjson_init(&j, buf, sizeof(buf)); // prepare writer
*
* vjson_obj_open(&j); // new object {...}
* vjson_keystr(&j, "key-str", str_value); // add 'key: "value"' to current object
* vjson_keyint(&j, "key-int", int_value); // add 'key: value' to current object
* vjson_key(&j, "key"); // add 'key: ' (for objects or arrays)
* vjson_arr_open(&j); // new array [...]
* vjson_str(&j, str_value); // add '"value"' to current array
* vjson_int(&j, int_value); // add 'value' to current array
* vjson_arr_close(&j); // close current array
* vjson_obj_close(&j); // close current object
*/
#include <stdio.h>

View File

@ -1,45 +1,58 @@
# TXTP format
TXTP is a text file with commands, to improve support for games using audio in certain uncommon or undesirable ways. It's in the form of a mini-playlist or a wrapper with play settings, meant to do post-processing over playable files.
TXTP is a text file with commands, to handle games using audio in uncommon or undesirable ways. It's a mini-playlist or a wrapper with play settings, meant to do post-processing over playable files.
Simply create a file named `(filename).txtp`, and inside write the song name and commands described below. Then open the new file directly and vgmstream should play it.
Simply create a file named `(any name).txtp`, and inside write the song and commands described below. Then open the new file directly and *vgmstream* should play it.
Common case examples:
## Common case examples
**stage01_intro+loop.txtp**
### join intro + loop segments
```
stage01_intro.vag
stage01_loop.vag
loop_mode = auto
```
**bgm01_subsong2.txtp**
### join channel layers
```
bgm01.fsb #2
bgm01_melody.hca
bgm01_vocals.hca
mode = layers
```
**sfx01-22khz.txtp**
### play subsong 5
```
sfx01.wav #h22050
bgm01.fsb #5
```
**field_channels3+4.txtp**
### play only channel 3 and 4
```
field.bfstm #C3,4
```
**bgm01.flac #I 10.0 .txtp**
### install loops (from 10 seconds to file end)
```
# (empty)
# this is a "mini-txtp" that sets loop start to 10 seconds
# notice it has the original filename + extension, then commands, then .txtp
bgm01.flac #I 10.0
```
**bgm01-loop-repeat.txtp**
### force sample rate to 22050hz
```
sfx01.wav #h 22050
```
### change play config to loop-repeat
```
bgm01.fsb #e
```
### double volume
```
bgm01.fsb #v 2.0
```
### mini txtp (empty .txtp with filename)
`bgm01.flac #I 10.0 .txtp`
## TXTP MODES
TXTP can join and play together multiple songs in various ways by setting a file list and mode:

View File

@ -1,6 +1,5 @@
#include "api_internal.h"
#include "mixing.h"
#if LIBVGMSTREAM_ENABLE
#define INTERNAL_BUF_SAMPLES 1024
@ -104,5 +103,3 @@ int api_get_sample_size(libvgmstream_sample_t sample_type) {
return 0x02;
}
}
#endif

View File

@ -1,7 +1,6 @@
#include "api_internal.h"
#include "sbuf.h"
#include "mixing.h"
#if LIBVGMSTREAM_ENABLE
static void load_vgmstream(libvgmstream_priv_t* priv, libvgmstream_options_t* opt) {
@ -106,14 +105,14 @@ static void update_format_info(libvgmstream_priv_t* priv) {
}
}
LIBVGMSTREAM_API int libvgmstream_open_song(libvgmstream_t* lib, libvgmstream_options_t* opt) {
LIBVGMSTREAM_API int libvgmstream_open_stream(libvgmstream_t* lib, libvgmstream_options_t* opt) {
if (!lib ||!lib->priv)
return LIBVGMSTREAM_ERROR_GENERIC;
if (!opt || !opt->libsf || opt->subsong_index < 0)
return LIBVGMSTREAM_ERROR_GENERIC;
// close loaded song if any + reset
libvgmstream_close_song(lib);
libvgmstream_close_stream(lib);
libvgmstream_priv_t* priv = lib->priv;
@ -131,7 +130,7 @@ LIBVGMSTREAM_API int libvgmstream_open_song(libvgmstream_t* lib, libvgmstream_op
}
LIBVGMSTREAM_API void libvgmstream_close_song(libvgmstream_t* lib) {
LIBVGMSTREAM_API void libvgmstream_close_stream(libvgmstream_t* lib) {
if (!lib || !lib->priv)
return;
@ -142,5 +141,3 @@ LIBVGMSTREAM_API void libvgmstream_close_song(libvgmstream_t* lib) {
libvgmstream_priv_reset(priv, true);
}
#endif

View File

@ -2,8 +2,6 @@
#include "mixing.h"
#include "render.h"
#if LIBVGMSTREAM_ENABLE
static bool reset_buf(libvgmstream_priv_t* priv) {
// state reset
@ -155,5 +153,3 @@ LIBVGMSTREAM_API void libvgmstream_reset(libvgmstream_t* lib) {
}
libvgmstream_priv_reset(priv, false);
}
#endif

View File

@ -1,5 +1,4 @@
#include "api_internal.h"
#if LIBVGMSTREAM_ENABLE
static int get_internal_log_level(libvgmstream_loglevel_t level) {
@ -60,7 +59,7 @@ LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_v
vgmstream_ctx_valid_cfg icfg = {
.is_extension = cfg->is_extension,
.skip_standard = cfg->skip_default,
.skip_standard = cfg->skip_standard,
.reject_extensionless = cfg->reject_extensionless,
.accept_unknown = cfg->accept_unknown,
.accept_common = cfg->accept_common
@ -91,5 +90,3 @@ LIBVGMSTREAM_API int libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_ti
LIBVGMSTREAM_API bool libvgmstream_is_virtual_filename(const char* filename) {
return vgmstream_is_virtual_filename(filename);
}
#endif

View File

@ -5,7 +5,6 @@
#include "../vgmstream.h"
#include "plugins.h"
#if LIBVGMSTREAM_ENABLE
#define LIBVGMSTREAM_OK 0
#define LIBVGMSTREAM_ERROR_GENERIC -1
@ -63,4 +62,3 @@ int api_get_sample_size(libvgmstream_sample_t sample_type);
STREAMFILE* open_api_streamfile(libstreamfile_t* libsf);
#endif
#endif

View File

@ -1,5 +1,4 @@
#include "api_internal.h"
#if LIBVGMSTREAM_ENABLE
static libstreamfile_t* libstreamfile_from_streamfile(STREAMFILE* sf);
@ -8,75 +7,75 @@ static libstreamfile_t* libstreamfile_from_streamfile(STREAMFILE* sf);
typedef struct {
int64_t offset;
int64_t size;
STREAMFILE* inner_sf;
STREAMFILE* sf;
char name[PATH_LIMIT];
} libsf_data_t;
} libsf_priv_t;
static int libsf_read(void* user_data, uint8_t* dst, int dst_size) {
libsf_data_t* data = user_data;
if (!data || !dst)
libsf_priv_t* priv = user_data;
if (!priv || !dst)
return 0;
int bytes = data->inner_sf->read(data->inner_sf, dst, data->offset, dst_size);
data->offset += bytes;
int bytes = priv->sf->read(priv->sf, dst, priv->offset, dst_size);
priv->offset += bytes;
return bytes;
}
static int64_t libsf_seek(void* user_data, int64_t offset, int whence) {
libsf_data_t* data = user_data;
if (!data)
libsf_priv_t* priv = user_data;
if (!priv)
return -1;
switch (whence) {
case LIBSTREAMFILE_SEEK_SET: /* absolute */
break;
case LIBSTREAMFILE_SEEK_CUR: /* relative to current */
offset += data->offset;
offset += priv->offset;
break;
case LIBSTREAMFILE_SEEK_END: /* relative to file end (should be negative) */
offset += data->size;
offset += priv->size;
break;
default:
break;
}
/* clamp offset like fseek */
if (offset > data->size)
offset = data->size;
if (offset > priv->size)
offset = priv->size;
else if (offset < 0)
offset = 0;
/* main seek */
data->offset = offset;
priv->offset = offset;
return 0;
}
static int64_t libsf_get_size(void* user_data) {
libsf_data_t* data = user_data;
if (!data)
libsf_priv_t* priv = user_data;
if (!priv)
return 0;
return data->size;
return priv->size;
}
static const char* libsf_get_name(void* user_data) {
libsf_data_t* data = user_data;
if (!data)
libsf_priv_t* priv = user_data;
if (!priv)
return NULL;
if (data->name[0] == '\0') {
data->inner_sf->get_name(data->inner_sf, data->name, sizeof(data->name));
if (priv->name[0] == '\0') {
priv->sf->get_name(priv->sf, priv->name, sizeof(priv->name));
}
return data->name;
return priv->name;
}
static libstreamfile_t* libsf_open(void* user_data, const char* filename) {
libsf_data_t* data = user_data;
if (!data || !data->inner_sf || !filename)
libsf_priv_t* priv = user_data;
if (!priv || !priv->sf || !filename)
return NULL;
STREAMFILE* sf = data->inner_sf->open(data->inner_sf, filename, 0);
STREAMFILE* sf = priv->sf->open(priv->sf, filename, 0);
if (!sf)
return NULL;
@ -93,11 +92,11 @@ static void libsf_close(libstreamfile_t* libsf) {
if (!libsf)
return;
libsf_data_t* data = libsf->user_data;
if (data && data->inner_sf) {
data->inner_sf->close(data->inner_sf);
libsf_priv_t* priv = libsf->user_data;
if (priv && priv->sf) {
priv->sf->close(priv->sf);
}
free(data);
free(priv);
free(libsf);
}
@ -106,7 +105,7 @@ static libstreamfile_t* libstreamfile_from_streamfile(STREAMFILE* sf) {
return NULL;
libstreamfile_t* libsf = NULL;
libsf_data_t* data = NULL;
libsf_priv_t* priv = NULL;
libsf = calloc(1, sizeof(libstreamfile_t));
if (!libsf) goto fail;
@ -118,12 +117,12 @@ static libstreamfile_t* libstreamfile_from_streamfile(STREAMFILE* sf) {
libsf->open = libsf_open;
libsf->close = libsf_close;
libsf->user_data = calloc(1, sizeof(libsf_data_t));
libsf->user_data = calloc(1, sizeof(libsf_priv_t));
if (!libsf->user_data) goto fail;
data = libsf->user_data;
data->inner_sf = sf;
data->size = get_streamfile_size(sf);
priv = libsf->user_data;
priv->sf = sf;
priv->size = get_streamfile_size(sf);
return libsf;
fail:
@ -145,4 +144,18 @@ LIBVGMSTREAM_API libstreamfile_t* libstreamfile_open_from_stdio(const char* file
return libsf;
}
#endif
LIBVGMSTREAM_API libstreamfile_t* libstreamfile_open_from_file(void* file_, const char* filename) {
FILE* file = file_;
STREAMFILE* sf = open_stdio_streamfile_by_file(file, filename);
if (!sf)
return NULL;
libstreamfile_t* libsf = libstreamfile_from_streamfile(sf);
if (!libsf) {
close_streamfile(sf);
return NULL;
}
return libsf;
}

195
src/base/api_libsf_cache.c Normal file
View File

@ -0,0 +1,195 @@
#include "api_internal.h"
/* libstreamfile_t for external use, that caches some external libsf */
/* value can be adjusted freely but 8k is a good enough compromise. */
#define CACHE_DEFAULT_BUFFER_SIZE 0x8000
typedef struct {
libstreamfile_t* libsf;
int64_t offset; /* last read offset (info) */
int64_t buf_offset; /* current buffer data start */
uint8_t* buf; /* data buffer */
size_t buf_size; /* max buffer size */
size_t valid_size; /* current buffer size */
size_t file_size; /* buffered file size */
char name[PATH_LIMIT];
} cache_priv_t;
static int cache_read(void* user_data, uint8_t* dst, int dst_size) {
cache_priv_t* priv = user_data;
size_t read_total = 0;
if (!dst || dst_size <= 0)
return 0;
/* is the part of the requested length in the buffer? */
if (priv->offset >= priv->buf_offset && priv->offset < priv->buf_offset + priv->valid_size) {
size_t buf_limit;
int buf_into = (int)(priv->offset - priv->buf_offset);
buf_limit = priv->valid_size - buf_into;
if (buf_limit > dst_size)
buf_limit = dst_size;
memcpy(dst, priv->buf + buf_into, buf_limit);
read_total += buf_limit;
dst_size -= buf_limit;
priv->offset += buf_limit;
dst += buf_limit;
}
/* read the rest of the requested length */
while (dst_size > 0) {
size_t buf_limit;
/* ignore requests at EOF */
if (priv->offset >= priv->file_size) {
//offset = priv->file_size; /* seems fseek doesn't clamp offset */
//VGM_ASSERT_ONCE(offset > sf->file_size, "STDIO: reading over file_size 0x%x @ 0x%lx + 0x%x\n", sf->file_size, offset, length);
break;
}
/* position to new offset */
priv->libsf->seek(priv, priv->offset, 0);
/* fill the buffer (offset now is beyond buf_offset) */
priv->buf_offset = priv->offset;
priv->valid_size = priv->libsf->read(priv, priv->buf, priv->buf_size);
/* decide how much must be read this time */
if (dst_size > priv->buf_size)
buf_limit = priv->buf_size;
else
buf_limit = dst_size;
/* give up on partial reads (EOF) */
if (priv->valid_size < buf_limit) {
memcpy(dst, priv->buf, priv->valid_size);
priv->offset += priv->valid_size;
read_total += priv->valid_size;
break;
}
/* use the new buffer */
memcpy(dst, priv->buf, buf_limit);
priv->offset += buf_limit;
read_total += buf_limit;
dst_size -= buf_limit;
dst += buf_limit;
}
return read_total;
}
static int64_t cache_seek(void* user_data, int64_t offset, int whence) {
cache_priv_t* priv = user_data;
switch (whence) {
case LIBSTREAMFILE_SEEK_SET: /* absolute */
break;
case LIBSTREAMFILE_SEEK_CUR: /* relative to current */
offset += priv->offset;
break;
case LIBSTREAMFILE_SEEK_END: /* relative to file end (should be negative) */
offset += priv->file_size;
break;
default:
break;
}
/* clamp offset like fseek */
if (offset > priv->file_size)
offset = priv->file_size;
else if (offset < 0)
offset = 0;
/* main seek */
priv->offset = offset;
return 0;
}
static int64_t cache_get_size(void* user_data) {
cache_priv_t* priv = user_data;
return priv->file_size;
}
static const char* cache_get_name(void* user_data) {
cache_priv_t* priv = user_data;
return priv->name;
}
static libstreamfile_t* cache_open(void* user_data, const char* filename) {
cache_priv_t* priv = user_data;
if (!priv || !priv->libsf || !filename)
return NULL;
libstreamfile_t* inner_libsf = priv->libsf->open(priv->libsf->user_data, filename);
if (!inner_libsf)
return NULL;
libstreamfile_t* libsf = libstreamfile_open_buffered(inner_libsf);
if (!libsf) {
libstreamfile_close(inner_libsf);
return NULL;
}
return libsf;
}
static void cache_close(libstreamfile_t* libsf) {
if (!libsf)
return;
cache_priv_t* priv = libsf->user_data;
if (priv && priv->libsf) {
libstreamfile_close(priv->libsf);
}
if (priv) {
free(priv->buf);
}
free(priv);
free(libsf);
}
LIBVGMSTREAM_API libstreamfile_t* libstreamfile_open_buffered(libstreamfile_t* ext_libsf) {
if (!ext_libsf)
return NULL;
// not selectable since vgmstream's read patterns don't really fit one buf size
int buf_size = CACHE_DEFAULT_BUFFER_SIZE;
libstreamfile_t* libsf = NULL;
cache_priv_t* priv = NULL;
libsf = calloc(1, sizeof(libstreamfile_t));
if (!libsf) goto fail;
libsf->read = cache_read;
libsf->seek = cache_seek;
libsf->get_size = cache_get_size;
libsf->get_name = cache_get_name;
libsf->open = cache_open;
libsf->close = cache_close;
libsf->user_data = calloc(1, sizeof(cache_priv_t));
if (!libsf->user_data) goto fail;
priv = libsf->user_data;
priv->libsf = ext_libsf;
priv->buf_size = buf_size;
priv->buf = calloc(buf_size, sizeof(uint8_t));
if (!priv->buf) goto fail;
priv->file_size = priv->libsf->get_size(priv->libsf->user_data);
snprintf(priv->name, sizeof(priv->name), "%s", priv->libsf->get_name(priv->libsf->user_data));
priv->name[sizeof(priv->name) - 1] = '\0';
return libsf;
fail:
cache_close(libsf);
return NULL;
}

View File

@ -1,5 +1,4 @@
#include "api_internal.h"
#if LIBVGMSTREAM_ENABLE
typedef struct {
VGMSTREAM_TAGS* vtags;
@ -65,5 +64,3 @@ LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags) {
free(tags->priv);
free(tags);
}
#endif

View File

@ -467,8 +467,8 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) {
return 1;
case coding_PCM4:
case coding_PCM4_U:
case coding_IMA_int:
case coding_DVI_IMA_int:
case coding_IMA_mono:
case coding_DVI_IMA_mono:
case coding_CAMELOT_IMA:
case coding_WV6_IMA:
case coding_HV_IMA:
@ -682,9 +682,9 @@ int decode_get_frame_size(VGMSTREAM* vgmstream) {
case coding_PCM4:
case coding_PCM4_U:
case coding_IMA:
case coding_IMA_int:
case coding_IMA_mono:
case coding_DVI_IMA:
case coding_DVI_IMA_int:
case coding_DVI_IMA_mono:
case coding_CAMELOT_IMA:
case coding_WV6_IMA:
case coding_HV_IMA:
@ -1314,13 +1314,13 @@ void decode_vgmstream(sbuf_t* sdst, VGMSTREAM* vgmstream, int samples_to_do) {
break;
case coding_IMA:
case coding_IMA_int:
case coding_IMA_mono:
case coding_DVI_IMA:
case coding_DVI_IMA_int: {
case coding_DVI_IMA_mono: {
int is_stereo = (vgmstream->channels > 1 && vgmstream->coding_type == coding_IMA)
|| (vgmstream->channels > 1 && vgmstream->coding_type == coding_DVI_IMA);
int is_high_first = vgmstream->coding_type == coding_DVI_IMA
|| vgmstream->coding_type == coding_DVI_IMA_int;
|| vgmstream->coding_type == coding_DVI_IMA_mono;
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_standard_ima(&vgmstream->ch[ch], buffer+ch,
vgmstream->channels, vgmstream->samples_into_block, samples_to_do, ch,

View File

@ -1,5 +1,4 @@
#include "api_internal.h"
#if LIBVGMSTREAM_ENABLE
/* STREAMFILE for internal use, that bridges calls to external libstreamfile_t */
@ -90,5 +89,3 @@ static STREAMFILE* open_api_streamfile_internal(libstreamfile_t* libsf, bool ext
STREAMFILE* open_api_streamfile(libstreamfile_t* libsf) {
return open_api_streamfile_internal(libsf, true);
}
#endif

View File

@ -108,15 +108,30 @@ ffmpeg_codec_data* init_ffmpeg_atrac3_riff(STREAMFILE* sf, off_t offset, int* p_
/* well behaved .at3 define "fact" but official tools accept files without it */
if (find_chunk_le(sf, get_id32be("fact"), offset + 0x0c,0, &fact_offset, &fact_size)) {
if (fact_size == 0x08) { /* early AT3 (mainly PSP games) */
if (fact_size == 0x08) {
/* early AT3 (mainly PSP games) */
fact_samples = read_s32le(fact_offset + 0x00, sf);
skip_samples = read_s32le(fact_offset + 0x04, sf); /* base skip samples */
}
else if (fact_size == 0x0c) { /* late AT3 (mainly PS3 games and few PSP games) */
else if (fact_size == 0x0c) {
/* late AT3 (mainly PS3 games and few PSP games) */
fact_samples = read_s32le(fact_offset + 0x00, sf);
/* 0x04: base skip samples, ignored by decoder */
skip_samples = read_s32le(fact_offset + 0x08, sf); /* skip samples with implicit skip of 184 added */
}
else if (fact_size == 0x04) {
/* some ATRAC3 in rare cases [Up (PSP), Flash Motor Karen (PSP)-SND0.at3, Harajuku Tantei Gakuen (PSP)] */
if (is_at3) // observed default vs sony's tools
skip_samples = 1024; // 1 frame
else if (is_at3p)
skip_samples = 2048; // 1 frame
else
skip_samples = 0;
fact_samples = read_s32le(fact_offset + 0x00, sf);
//TODO: seems like some at3 made by soundforge may define more fact samples than possible;
// original tools only decode up to EOF [Up (PSP)]
}
else {
VGM_LOG("ATRAC3: unknown fact size\n");
goto fail;
@ -125,9 +140,9 @@ ffmpeg_codec_data* init_ffmpeg_atrac3_riff(STREAMFILE* sf, off_t offset, int* p_
else {
fact_samples = 0; /* tools output 0 samples in this case unless loop end is defined */
if (is_at3)
skip_samples = 1024; /* 1 frame */
skip_samples = 1024; // 1 frame
else if (is_at3p)
skip_samples = 2048; /* 1 frame */
skip_samples = 2048; // 1 frame
else
skip_samples = 0;
}

View File

@ -14,17 +14,17 @@
// default number of quantized coefficients encoded per band, for each bitrate modes
static const int BAND_CODES[MAX_BITRATES][MAX_BANDS] = {
{5, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, },
{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, },
{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, },
{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, },
{5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 5, 5, 5, 5, 5, 5, },
{5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, },
{5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, },
{5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, },
{5, 5, 5, 5, 5, 5, 5, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, },
{5, 5, 5, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, },
{5, 5, 5, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, },
{5, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, },
{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, },
{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, },
{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, },
{5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 5, 5, 5, 5, 5, 5, },
{5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, },
{5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, },
{5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, },
{5, 5, 5, 5, 5, 5, 5, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, },
{5, 5, 5, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, },
{5, 5, 5, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, },
};
// Number of modified coefs to be added/substracted to some bands, for each bitrate mode (varies per frame)
@ -43,7 +43,7 @@ static const int BAND_STEPS[MAX_BANDS] = {
// lower bands are 0 since all tables above are fixed to 8
static const int BAND_STEP_BITS[MAX_BANDS] = {
0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 7, 7,
0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 7, 7,
};
// 360 cosine, close to: for (0..256) t[i] = cos(2 * PI * i / points) with some rounding?

View File

@ -22,218 +22,218 @@ struct mp4_aac_codec_data {
MP4FileHandle h_mp4file;
MP4TrackId track_id;
unsigned long sampleId;
unsigned long numSamples;
unsigned long numSamples;
UINT codec_init_data_size;
HANDLE_AACDECODER h_aacdecoder;
unsigned int sample_ptr;
unsigned int samples_per_frame
unsigned int samples_discard;
unsigned int samples_per_frame
unsigned int samples_discard;
INT_PCM sample_buffer[( (6) * (2048)*4 )];
};
// VGM_USE_MP4V2
static void* mp4_file_open( const char* name, MP4FileMode mode ) {
char * endptr;
char * endptr;
#ifdef _MSC_VER
unsigned __int64 ptr = _strtoui64( name, &endptr, 16 );
unsigned __int64 ptr = _strtoui64( name, &endptr, 16 );
#else
unsigned long ptr = strtoul( name, &endptr, 16 );
unsigned long ptr = strtoul( name, &endptr, 16 );
#endif
return (void*) ptr;
return (void*) ptr;
}
static int mp4_file_seek( void* handle, int64_t pos ) {
mp4_streamfile * file = ( mp4_streamfile * ) handle;
if ( pos > file->size ) pos = file->size;
pos += file->start;
file->offset = pos;
return 0;
mp4_streamfile * file = ( mp4_streamfile * ) handle;
if ( pos > file->size ) pos = file->size;
pos += file->start;
file->offset = pos;
return 0;
}
static int mp4_file_get_size( void* handle, int64_t* size ) {
mp4_streamfile * file = ( mp4_streamfile * ) handle;
*size = file->size;
return 0;
mp4_streamfile * file = ( mp4_streamfile * ) handle;
*size = file->size;
return 0;
}
static int mp4_file_read( void* handle, void* buffer, int64_t size, int64_t* nin, int64_t maxChunkSize ) {
mp4_streamfile * file = ( mp4_streamfile * ) handle;
int64_t max_size = file->size - file->offset - file->start;
if ( size > max_size ) size = max_size;
if ( size > 0 )
{
*nin = read_streamfile( (uint8_t *) buffer, file->offset, size, file->streamfile );
file->offset += *nin;
}
else
{
*nin = 0;
return 1;
}
return 0;
mp4_streamfile * file = ( mp4_streamfile * ) handle;
int64_t max_size = file->size - file->offset - file->start;
if ( size > max_size ) size = max_size;
if ( size > 0 )
{
*nin = read_streamfile( (uint8_t *) buffer, file->offset, size, file->streamfile );
file->offset += *nin;
}
else
{
*nin = 0;
return 1;
}
return 0;
}
static int mp4_file_write( void* handle, const void* buffer, int64_t size, int64_t* nout, int64_t maxChunkSize ) {
return 1;
return 1;
}
static int mp4_file_close( void* handle ) {
return 0;
return 0;
}
MP4FileProvider mp4_file_provider = { mp4_file_open, mp4_file_seek, mp4_file_read, mp4_file_write, mp4_file_close, mp4_file_get_size };
mp4_aac_codec_data* init_mp4_aac(STREAMFILE* sf) {
char filename[PATH_LIMIT];
uint32_t start = 0;
uint32_t size = get_streamfile_size(sf);
char filename[PATH_LIMIT];
uint32_t start = 0;
uint32_t size = get_streamfile_size(sf);
CStreamInfo* stream_info = NULL;
CStreamInfo* stream_info = NULL;
uint8_t* buffer = NULL;
uint32_t buffer_size;
UINT ubuffer_size, bytes_valid;
uint8_t* buffer = NULL;
uint32_t buffer_size;
UINT ubuffer_size, bytes_valid;
mp4_aac_codec_data* data = calloc(1, sizeof(mp4_aac_codec_data));
if (!data) goto fail;
mp4_aac_codec_data* data = calloc(1, sizeof(mp4_aac_codec_data));
if (!data) goto fail;
data->if_file.streamfile = sf;
data->if_file.start = start;
data->if_file.offset = start;
data->if_file.size = size;
data->if_file.streamfile = sf;
data->if_file.start = start;
data->if_file.offset = start;
data->if_file.size = size;
/* Big ol' kludge! */
sprintf( filename, "%p", &data->if_file );
data->h_mp4file = MP4ReadProvider( filename, &mp4_file_provider );
if ( !data->h_mp4file ) goto fail;
/* Big ol' kludge! */
sprintf( filename, "%p", &data->if_file );
data->h_mp4file = MP4ReadProvider( filename, &mp4_file_provider );
if ( !data->h_mp4file ) goto fail;
if ( MP4GetNumberOfTracks(data->h_mp4file, MP4_AUDIO_TRACK_TYPE, '\000') != 1 ) goto fail;
if ( MP4GetNumberOfTracks(data->h_mp4file, MP4_AUDIO_TRACK_TYPE, '\000') != 1 ) goto fail;
data->track_id = MP4FindTrackId( data->h_mp4file, 0, MP4_AUDIO_TRACK_TYPE, '\000' );
data->track_id = MP4FindTrackId( data->h_mp4file, 0, MP4_AUDIO_TRACK_TYPE, '\000' );
data->h_aacdecoder = aacDecoder_Open( TT_MP4_RAW, 1 );
if ( !data->h_aacdecoder ) goto fail;
data->h_aacdecoder = aacDecoder_Open( TT_MP4_RAW, 1 );
if ( !data->h_aacdecoder ) goto fail;
MP4GetTrackESConfiguration( data->h_mp4file, data->track_id, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size));
MP4GetTrackESConfiguration( data->h_mp4file, data->track_id, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size));
ubuffer_size = buffer_size;
if ( aacDecoder_ConfigRaw( data->h_aacdecoder, &buffer, &ubuffer_size ) ) goto fail;
ubuffer_size = buffer_size;
if ( aacDecoder_ConfigRaw( data->h_aacdecoder, &buffer, &ubuffer_size ) ) goto fail;
free( buffer ); buffer = NULL;
free( buffer ); buffer = NULL;
data->sampleId = 1;
data->numSamples = MP4GetTrackNumberOfSamples( data->h_mp4file, data->track_id );
data->sampleId = 1;
data->numSamples = MP4GetTrackNumberOfSamples( data->h_mp4file, data->track_id );
if (!MP4ReadSample(data->h_mp4file, data->track_id, data->sampleId, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size), 0, 0, 0, 0)) goto fail;
if (!MP4ReadSample(data->h_mp4file, data->track_id, data->sampleId, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size), 0, 0, 0, 0)) goto fail;
ubuffer_size = buffer_size;
bytes_valid = buffer_size;
if ( aacDecoder_Fill( data->h_aacdecoder, &buffer, &ubuffer_size, &bytes_valid ) || bytes_valid ) goto fail;
if ( aacDecoder_DecodeFrame( data->h_aacdecoder, data->sample_buffer, ( (6) * (2048)*4 ), 0 ) ) goto fail;
ubuffer_size = buffer_size;
bytes_valid = buffer_size;
if ( aacDecoder_Fill( data->h_aacdecoder, &buffer, &ubuffer_size, &bytes_valid ) || bytes_valid ) goto fail;
if ( aacDecoder_DecodeFrame( data->h_aacdecoder, data->sample_buffer, ( (6) * (2048)*4 ), 0 ) ) goto fail;
free( buffer ); buffer = NULL;
free( buffer ); buffer = NULL;
data->sample_ptr = 0;
data->sample_ptr = 0;
stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
data->samples_per_frame = stream_info->frameSize;
data->samples_discard = 0;
data->samples_per_frame = stream_info->frameSize;
data->samples_discard = 0;
sf->get_name( sf, filename, sizeof(filename) );
sf->get_name( sf, filename, sizeof(filename) );
data->if_file.streamfile = sf->open(sf, filename, 0);
if (!data->if_file.streamfile) goto fail;
data->if_file.streamfile = sf->open(sf, filename, 0);
if (!data->if_file.streamfile) goto fail;
return data;
return data;
fail:
free( buffer ); buffer = NULL;
free_mp4_aac(data);
free( buffer ); buffer = NULL;
free_mp4_aac(data);
}
static void convert_samples(INT_PCM * src, sample_t* dest, int32_t count) {
int32_t i;
for ( i = 0; i < count; i++ ) {
INT_PCM sample = *src++;
sample >>= SAMPLE_BITS - 16;
if ( ( sample + 0x8000 ) & 0xFFFF0000 ) sample = 0x7FFF ^ ( sample >> 31 );
*dest++ = sample;
}
int32_t i;
for ( i = 0; i < count; i++ ) {
INT_PCM sample = *src++;
sample >>= SAMPLE_BITS - 16;
if ( ( sample + 0x8000 ) & 0xFFFF0000 ) sample = 0x7FFF ^ ( sample >> 31 );
*dest++ = sample;
}
}
void decode_mp4_aac(mp4_aac_codec_data * data, sample_t* outbuf, int32_t samples_to_do, int channels) {
int samples_done = 0;
int samples_done = 0;
uint8_t * buffer = NULL;
uint32_t buffer_size;
UINT ubuffer_size, bytes_valid;
uint8_t * buffer = NULL;
uint32_t buffer_size;
UINT ubuffer_size, bytes_valid;
CStreamInfo * stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
CStreamInfo * stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
int32_t samples_remain = data->samples_per_frame - data->sample_ptr;
int32_t samples_remain = data->samples_per_frame - data->sample_ptr;
if ( data->samples_discard ) {
if ( samples_remain <= data->samples_discard ) {
data->samples_discard -= samples_remain;
samples_remain = 0;
}
else {
samples_remain -= data->samples_discard;
data->sample_ptr += data->samples_discard;
data->samples_discard = 0;
}
}
if ( data->samples_discard ) {
if ( samples_remain <= data->samples_discard ) {
data->samples_discard -= samples_remain;
samples_remain = 0;
}
else {
samples_remain -= data->samples_discard;
data->sample_ptr += data->samples_discard;
data->samples_discard = 0;
}
}
if ( samples_remain > samples_to_do ) samples_remain = samples_to_do;
if ( samples_remain > samples_to_do ) samples_remain = samples_to_do;
convert_samples( data->sample_buffer + data->sample_ptr * stream_info->numChannels, outbuf, samples_remain * stream_info->numChannels );
convert_samples( data->sample_buffer + data->sample_ptr * stream_info->numChannels, outbuf, samples_remain * stream_info->numChannels );
outbuf += samples_remain * stream_info->numChannels;
outbuf += samples_remain * stream_info->numChannels;
data->sample_ptr += samples_remain;
data->sample_ptr += samples_remain;
samples_done += samples_remain;
samples_done += samples_remain;
while ( samples_done < samples_to_do ) {
if (data->sampleId >= data->numSamples) {
memset(outbuf, 0, (samples_to_do - samples_done) * stream_info->numChannels * sizeof(sample_t));
break;
}
if (!MP4ReadSample( data->h_mp4file, data->track_id, ++data->sampleId, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size), 0, 0, 0, 0)) return;
ubuffer_size = buffer_size;
bytes_valid = buffer_size;
if ( aacDecoder_Fill( data->h_aacdecoder, &buffer, &ubuffer_size, &bytes_valid ) || bytes_valid ) {
free( buffer );
return;
}
if ( aacDecoder_DecodeFrame( data->h_aacdecoder, data->sample_buffer, ( (6) * (2048)*4 ), 0 ) ) {
free( buffer );
return;
}
free( buffer ); buffer = NULL;
stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
samples_remain = data->samples_per_frame = stream_info->frameSize;
data->sample_ptr = 0;
if ( data->samples_discard ) {
if ( samples_remain <= data->samples_discard ) {
data->samples_discard -= samples_remain;
samples_remain = 0;
}
else {
samples_remain -= data->samples_discard;
data->sample_ptr = data->samples_discard;
data->samples_discard = 0;
}
}
if ( samples_remain > samples_to_do - samples_done ) samples_remain = samples_to_do - samples_done;
convert_samples( data->sample_buffer + data->sample_ptr * stream_info->numChannels, outbuf, samples_remain * stream_info->numChannels );
samples_done += samples_remain;
outbuf += samples_remain * stream_info->numChannels;
data->sample_ptr = samples_remain;
}
while ( samples_done < samples_to_do ) {
if (data->sampleId >= data->numSamples) {
memset(outbuf, 0, (samples_to_do - samples_done) * stream_info->numChannels * sizeof(sample_t));
break;
}
if (!MP4ReadSample( data->h_mp4file, data->track_id, ++data->sampleId, (uint8_t**)(&buffer), (uint32_t*)(&buffer_size), 0, 0, 0, 0)) return;
ubuffer_size = buffer_size;
bytes_valid = buffer_size;
if ( aacDecoder_Fill( data->h_aacdecoder, &buffer, &ubuffer_size, &bytes_valid ) || bytes_valid ) {
free( buffer );
return;
}
if ( aacDecoder_DecodeFrame( data->h_aacdecoder, data->sample_buffer, ( (6) * (2048)*4 ), 0 ) ) {
free( buffer );
return;
}
free( buffer ); buffer = NULL;
stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
samples_remain = data->samples_per_frame = stream_info->frameSize;
data->sample_ptr = 0;
if ( data->samples_discard ) {
if ( samples_remain <= data->samples_discard ) {
data->samples_discard -= samples_remain;
samples_remain = 0;
}
else {
samples_remain -= data->samples_discard;
data->sample_ptr = data->samples_discard;
data->samples_discard = 0;
}
}
if ( samples_remain > samples_to_do - samples_done ) samples_remain = samples_to_do - samples_done;
convert_samples( data->sample_buffer + data->sample_ptr * stream_info->numChannels, outbuf, samples_remain * stream_info->numChannels );
samples_done += samples_remain;
outbuf += samples_remain * stream_info->numChannels;
data->sample_ptr = samples_remain;
}
}
@ -265,9 +265,9 @@ void free_mp4_aac(mp4_aac_codec_data * data) {
}
void mp4_aac_get_streamfile(mp4_aac_codec_data* data) {
if (!data)
return NULL;
return data->if_file.streamfile;
if (!data)
return NULL;
return data->if_file.streamfile;
}
int32_t mp4_aac_get_samples(mp4_aac_codec_data* data) {
@ -286,7 +286,7 @@ int mp4_aac_get_sample_rate(mp4_aac_codec_data* data) {
if (!data)
return 0;
CStreamInfo* stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
CStreamInfo* stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
return stream_info->sample_rate;
}
@ -294,8 +294,8 @@ int mp4_aac_get_channels(mp4_aac_codec_data* data) {
if (!data)
return 0;
CStreamInfo* stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
return stream_info->numChannels;
CStreamInfo* stream_info = aacDecoder_GetStreamInfo( data->h_aacdecoder );
return stream_info->numChannels;
}
#endif

View File

@ -144,8 +144,13 @@ int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL* stream, mpeg_codec_data* d
current_interleave_pre = current_interleave*num_stream;
current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre;
if (stream->offset >= data->config.data_size) {
VGM_LOG_ONCE("MPEG: fsb overread\n");
return false;
}
if (!mpeg_get_frame_info(stream->streamfile, stream->offset + current_interleave_pre, &info))
goto fail;
return false;
current_data_size = info.frame_size;
/* get FSB padding for Layer III or multichannel Layer II (Layer I isn't supported by FMOD).
@ -207,7 +212,7 @@ int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL* stream, mpeg_codec_data* d
else if (ms->current_size_count == data->config.max_chunks)
current_interleave = data->config.interleave_last;
else
goto fail;
return false;
current_interleave_pre = current_interleave*num_stream;
current_interleave_post = current_interleave*(data->streams_size-1) - current_interleave_pre;
@ -218,14 +223,14 @@ int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL* stream, mpeg_codec_data* d
break;
default: /* standard frames (CBR or VBR) */
if ( !mpeg_get_frame_info(stream->streamfile, stream->offset, &info) )
goto fail;
if (!mpeg_get_frame_info(stream->streamfile, stream->offset, &info))
return false;
current_data_size = info.frame_size;
break;
}
if (!current_data_size || current_data_size > ms->buffer_size) {
VGM_LOG("MPEG: incorrect data_size 0x%x vs buffer 0x%x\n", current_data_size, ms->buffer_size);
goto fail;
return false;
}
/* This assumes all streams' offsets start in the first stream, and advances
@ -248,9 +253,7 @@ int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL* stream, mpeg_codec_data* d
}
return 1;
fail:
return 0;
return true;
}
#endif
@ -359,7 +362,6 @@ uint32_t mpeg_get_tag_size(STREAMFILE* sf, uint32_t offset, uint32_t header) {
frame_size += 0x0a;
return frame_size;
}
/* skip ID3v1 */

View File

@ -408,9 +408,11 @@ static const char* extension_list[] = {
"nxa",
"nxopus",
"oga",
//"ogg", //common
"ogg_",
"ogl",
"ogs",
"ogv",
"oma", //FFmpeg/not parsed (ATRAC3/ATRAC3PLUS/MP3/LPCM/WMA)
"omu",
@ -683,6 +685,7 @@ static const char* extension_list[] = {
"xau",
"xav",
"xb", //txth/reserved [Scooby-Doo! Unmasked (Xbox)]
"xhd",
"xen",
"xma",
"xma2",
@ -833,9 +836,9 @@ static const coding_info coding_info_list[] = {
{coding_EA_XAS_V1, "Electronic Arts EA-XAS 4-bit ADPCM v1"},
{coding_IMA, "IMA 4-bit ADPCM"},
{coding_IMA_int, "IMA 4-bit ADPCM (mono/interleave)"},
{coding_IMA_mono, "IMA 4-bit ADPCM (mono)"},
{coding_DVI_IMA, "Intel DVI 4-bit IMA ADPCM"},
{coding_DVI_IMA_int, "Intel DVI 4-bit IMA ADPCM (mono/interleave)"},
{coding_DVI_IMA_mono, "Intel DVI 4-bit IMA ADPCM (mono)"},
{coding_CAMELOT_IMA, "Camelot IMA 4-bit ADPCM"},
{coding_SNDS_IMA, "Heavy Iron .snds 4-bit IMA ADPCM"},
{coding_QD_IMA, "Quantic Dream 4-bit IMA ADPCM"},
@ -1355,7 +1358,7 @@ static const meta_info meta_info_list[] = {
{meta_AIF_ASOBO, "Asobo Studio .AIF header"},
{meta_AO, "AlphaOgg .AO header"},
{meta_APC, "Cryo APC header"},
{meta_WV2, "Infogrames North America WAV2 header"},
{meta_WAV2, "Infogrames North America WAV2 header"},
{meta_XAU_KONAMI, "Konami XAU header"},
{meta_DERF, "Xilam DERF header"},
{meta_UTK, "Maxis UTK header"},
@ -1462,6 +1465,7 @@ static const meta_info meta_info_list[] = {
{meta_PPHD, "Sony PPHD header"},
{meta_XABP, "cavia XABp header"},
{meta_I3DS, "Codemasters i3DS header"},
{meta_AXHD, "Angel Studios AXHD header"},
};
void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) {

View File

@ -1,18 +1,16 @@
#ifndef _LIBVGMSTREAM_H_
#define _LIBVGMSTREAM_H_
#define LIBVGMSTREAM_ENABLE 1
#if LIBVGMSTREAM_ENABLE
/* libvgmstream: vgmstream's public API
*
* Basic usage (also see api_example.c):
* - libvgmstream_init(...) // create context
* - libvgmstream_setup(...) // setup config (if needed)
* - libvgmstream_open_song(...) // open format
* - libvgmstream_render(...) // main decode
* - output samples + repeat libvgmstream_render until stream is done
* - libvgmstream_free(...) // cleanup
* - libvgmstream_init(...) // create context
* - libvgmstream_setup(...) // setup config (if needed)
* - libvgmstream_open_stream(...) // open format
* - libvgmstream_render(...) // main decode
* - output samples + repeat libvgmstream_render until 'done' is set
* - libvgmstream_free(...) // cleanup
*
* By default vgmstream behaves like a decoder (returns samples until stream end), but you can configure
* it to loop N times or even downmix. In other words, it also behaves a bit like a player.
@ -56,9 +54,9 @@
* - only refers to the API itself, changes related to formats/etc don't alter this
* - vgmstream's features are mostly stable, but this API may be tweaked from time to time
*/
#define LIBVGMSTREAM_API_VERSION_MAJOR 1 // breaking API/ABI changes
#define LIBVGMSTREAM_API_VERSION_MINOR 0 // compatible API/ABI changes
#define LIBVGMSTREAM_API_VERSION_PATCH 0 // fixes
#define LIBVGMSTREAM_API_VERSION_MAJOR 0x01 // breaking API/ABI changes
#define LIBVGMSTREAM_API_VERSION_MINOR 0x00 // compatible API/ABI changes
#define LIBVGMSTREAM_API_VERSION_PATCH 0x00 // fixes
/* Current API version, for dynamic checks. returns hex value: 0xMMmmpppp = MM-major, mm-minor, pppp-patch
* - use when loading vgmstream as a dynamic library to ensure API/ABI compatibility
@ -75,10 +73,10 @@ LIBVGMSTREAM_API uint32_t libvgmstream_get_version(void);
/* interleaved samples: buf[0]=ch0, buf[1]=ch1, buf[2]=ch0, buf[3]=ch0, ... */
typedef enum {
LIBVGMSTREAM_SAMPLE_PCM16 = 0x01,
LIBVGMSTREAM_SAMPLE_PCM24 = 0x02,
LIBVGMSTREAM_SAMPLE_PCM32 = 0x03,
LIBVGMSTREAM_SAMPLE_FLOAT = 0x04,
LIBVGMSTREAM_SAMPLE_PCM16 = 1,
LIBVGMSTREAM_SAMPLE_PCM24 = 2,
LIBVGMSTREAM_SAMPLE_PCM32 = 3,
LIBVGMSTREAM_SAMPLE_FLOAT = 4,
} libvgmstream_sample_t;
/* current song info, may be copied around (values are info-only) */
@ -86,12 +84,11 @@ typedef struct {
/* main (always set) */
int channels; // output channels
int sample_rate; // output sample rate
libvgmstream_sample_t sample_type; // output buffer's sample type
int sample_size; // derived from sample_type (pcm16=0x02, float=0x04, etc)
/* extra info (may be 0 if not known or not relevant) */
uint32_t channel_layout; // standard WAVE bitflags
uint32_t channel_layout; // standard WAVE bitflags, 0 if unset or non-standard
int subsong_index; // 0 = none, N = loaded subsong N (1=first)
int subsong_count; // 0 = format has no concept of subsongs, N = has N subsongs
@ -110,6 +107,7 @@ typedef struct {
bool loop_flag; // if file loops
// ** false + defined loops means looping was forcefully disabled
// ** true + undefined loops means the file loops in a way not representable by loop points
//bool rough_samples; // signal cases where loop points or sample count can't exactly reflect actual behavior
bool play_forever; // if file loops forever based on current config (meaning _play never stops)
int64_t play_samples; // totals after all calculations (after applying loop/fade/etc config)
@ -117,32 +115,29 @@ typedef struct {
// ** if play_forever is set this is still provided for reference based on non-forever config
int stream_bitrate; // average bitrate of the subsong (slightly bloated vs codec_bitrate; incorrect in rare cases)
//int codec_bitrate; // average bitrate of the codec data
// ** not possible / slow to calculate in most cases
//int codec_bitrate; // average bitrate of the codec data [not possible / slow to calculate in most cases]
/* descriptions */
char codec_name[128]; //
char layout_name[128]; //
char meta_name[128]; // (not internal "tag" metadata)
char stream_name[256]; // some internal name or representation, not always useful
// ** these are a bit big for a struct, but the typical use case of vgsmtream is opening a file > immediately
char codec_name[128]; // represention of main decoder name
char layout_name[128]; // represention of how data is laid out
char meta_name[128]; // represention of format's name (not internal "tag" metadata)
char stream_name[256]; // stream's internal name or representation (not an internal filename not always useful)
// ** these are a bit big for a struct, but the typical use case of vgmstream is opening a file > immediately
// query description and since libvgmstream returns its own copy it shouldn't be too much of a problem
// ** (may be separated later)
/* misc */
//bool rough_samples; // signal cases where loop points or sample count can't exactly reflect actual behavior
int format_id; // when reopening subfiles or similar formats without checking other all possible formats
// ** this value WILL change without warning between vgmstream versions/commits
// ** this value WILL change without warning between vgmstream versions/commits, but usually only add
} libvgmstream_format_t;
/* current decoder state */
typedef struct {
void* buf; // current decoded buf (valid after _decode until next call; may change between calls)
int buf_samples; // current buffer samples (0 is possible in some cases)
int buf_bytes; // current buffer bytes (channels * sample_size * samples)
bool done; // when stream is done based on config
bool done; // when stream is done, based on config
// ** note that with play_forever this flag is never set
} libvgmstream_decoder_t;
@ -183,7 +178,7 @@ typedef struct {
double fade_time; // fade period after target loops
double fade_delay; // fade delay after target loops
int auto_downmix_channels; // downmixing if vgmstream's channels are higher than value
int auto_downmix_channels; // downmix if vgmstream's channels are higher than value
// ** for players that can only handle N channels
// ** this type of downmixing is very simplistic and not recommended
@ -218,11 +213,11 @@ typedef struct {
* - returns < 0 on error (file not recognised, invalid subsong index, etc)
* - will close currently loaded song if needed
*/
LIBVGMSTREAM_API int libvgmstream_open_song(libvgmstream_t* lib, libvgmstream_options_t* open_options);
LIBVGMSTREAM_API int libvgmstream_open_stream(libvgmstream_t* lib, libvgmstream_options_t* open_options);
/* Closes current song; may still use libvgmstream to open other songs
*/
LIBVGMSTREAM_API void libvgmstream_close_song(libvgmstream_t* lib);
LIBVGMSTREAM_API void libvgmstream_close_stream(libvgmstream_t* lib);
/* Decodes next batch of samples
@ -289,14 +284,14 @@ LIBVGMSTREAM_API const char** libvgmstream_get_common_extensions(size_t* size);
typedef struct {
bool is_extension; /* set if filename is just an extension */
bool skip_default; /* set if shouldn't check default formats */
bool reject_extensionless; /* set if player can't play extensionless files */
bool accept_unknown; /* set to allow any extension (for txth) */
bool accept_common; /* set to allow known-but-common extension (when player has plugin priority) */
bool is_extension; /* set if filename is just an extension (otherwise may be seen as 'extensionless') */
bool skip_standard; /* disable extension check vs default formats */
bool reject_extensionless; /* enable if player can't play extensionless files */
bool accept_unknown; /* enable to allow any extension even if not known by vgmstream (for .txth) */
bool accept_common; /* enable to allow known-but-common extension (when player has plugin priority) */
} libvgmstream_valid_t;
/* Returns if vgmstream can parse a filename by extension, to reject some files earlier
/* Returns if vgmstream can parse a filename by extension, to reject some files earlier.
* - doesn't check file contents (that's only done on _open)
* - config may be NULL
* - mainly for plugins that want to fail early; libvgmstream doesn't use this
@ -363,4 +358,3 @@ LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags);
#endif
#endif

View File

@ -221,6 +221,7 @@
<ClCompile Include="base\api_decode_play.c" />
<ClCompile Include="base\api_helpers.c" />
<ClCompile Include="base\api_libsf.c" />
<ClCompile Include="base\api_libsf_cache.c" />
<ClCompile Include="base\api_tags.c" />
<ClCompile Include="base\decode.c" />
<ClCompile Include="base\info.c" />
@ -421,6 +422,7 @@
<ClCompile Include="meta\awb.c" />
<ClCompile Include="meta\awc.c" />
<ClCompile Include="meta\awd.c" />
<ClCompile Include="meta\axhd.c" />
<ClCompile Include="meta\baf.c" />
<ClCompile Include="meta\bar.c" />
<ClCompile Include="meta\bcstm.c" />
@ -761,6 +763,7 @@
<ClCompile Include="meta\vxn.c" />
<ClCompile Include="meta\wady.c" />
<ClCompile Include="meta\waf.c" />
<ClCompile Include="meta\wav2.c" />
<ClCompile Include="meta\wave.c" />
<ClCompile Include="meta\wavebatch.c" />
<ClCompile Include="meta\wave_segmented.c" />
@ -771,7 +774,6 @@
<ClCompile Include="meta\wpd.c" />
<ClCompile Include="meta\wsi.c" />
<ClCompile Include="meta\ws_aud.c" />
<ClCompile Include="meta\wv2.c" />
<ClCompile Include="meta\wv6.c" />
<ClCompile Include="meta\wvs.c" />
<ClCompile Include="meta\wwise.c" />

View File

@ -493,6 +493,9 @@
<ClCompile Include="base\api_libsf.c">
<Filter>base\Source Files</Filter>
</ClCompile>
<ClCompile Include="base\api_libsf_cache.c">
<Filter>base\Source Files</Filter>
</ClCompile>
<ClCompile Include="base\api_tags.c">
<Filter>base\Source Files</Filter>
</ClCompile>
@ -1093,6 +1096,9 @@
<ClCompile Include="meta\awd.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\axhd.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\baf.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
@ -2113,6 +2119,9 @@
<ClCompile Include="meta\waf.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\wav2.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\wave.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
@ -2143,9 +2152,6 @@
<ClCompile Include="meta\ws_aud.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\wv2.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\wv6.c">
<Filter>meta\Source Files</Filter>
</ClCompile>

View File

@ -1,13 +1,15 @@
#ifndef _LIBVGMSTREAM_STREAMFILE_H_
#define _LIBVGMSTREAM_STREAMFILE_H_
#include "libvgmstream.h"
#if LIBVGMSTREAM_ENABLE
/* vgmstream's IO API, defined as a "streamfile" (SF).
/* vgmstream's IO API, defined as a "streamfile" ('SF').
*
* vgmstream roughly assumes there is an underlying filesystem (as usual in games): seeking + reading from arbitrary offsets,
* opening companion files, filename tests, etc. If your case is too different you may still create a partial streamfile: returning
* a fake filename, only handling "open" that reopens itself (same filename), etc. Simpler formats will probably work just fine.
* vgmstream mostly assumes there is an underlying filesystem (as usual in games), plus given video game formats are
* often ill-defined it needs extra ops to handle edge cases: seeking + reading from arbitrary offsets, opening companion
* files, filename/size tests, etc.
*
* If your case is too different you may still create a partial streamfile: returning a fake filename, only handling "open"
* that reopens itself (same filename), etc. Simpler formats should work fine.
*/
@ -19,7 +21,7 @@ enum {
//LIBSTREAMFILE_SEEK_GET_SIZE = 5,
};
// maybe "libvgmstream_streamfile_t" but it was getting unwieldly
// should be "libvgmstream_streamfile_t" but it was getting unwieldly
typedef struct libstreamfile_t {
//uint32_t flags; // info flags for vgmstream
void* user_data; // any internal structure
@ -30,11 +32,11 @@ typedef struct libstreamfile_t {
int (*read)(void* user_data, uint8_t* dst, int dst_size);
/* seek to offset
* - note that vgmstream needs to seek + read fairly often (to be optimized later)
* - note that vgmstream needs to seek + read fairly often (to be optimized someday)
*/
int64_t (*seek)(void* user_data, int64_t offset, int whence);
/* get max offset (typically for checks or calculations)
/* get max offset (typically for checks or sample calculations)
*/
int64_t (*get_size)(void* user_data);
@ -47,7 +49,7 @@ typedef struct libstreamfile_t {
*/
struct libstreamfile_t* (*open)(void* user_data, const char* filename);
/* free current SF (needed for copied streamfiles) */
/* free current SF */
void (*close)(struct libstreamfile_t* libsf);
} libstreamfile_t;
@ -60,8 +62,13 @@ static inline void libstreamfile_close(libstreamfile_t* libsf) {
libsf->close(libsf);
}
/* base libstreamfile using STDIO (cached) */
LIBVGMSTREAM_API libstreamfile_t* libstreamfile_open_from_stdio(const char* filename);
#endif
/* base libstreamfile using a FILE (cached); the filename is needed as metadata */
LIBVGMSTREAM_API libstreamfile_t* libstreamfile_open_from_file(void* file, const char* filename);
/* cached streamfile (recommended to wrap your external libsf since vgmstream needs to seek a lot) */
LIBVGMSTREAM_API libstreamfile_t* libstreamfile_open_buffered(libstreamfile_t* ext_libsf);
#endif

View File

@ -1,6 +1,6 @@
#include "meta.h"
/* ADP - from Wildfire Studios games [Balls of Steel (PC)] */
/* ADP! - from Wildfire Studios games [Balls of Steel (PC)] */
VGMSTREAM* init_vgmstream_adp_wildfire(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
uint32_t start_offset;
@ -28,7 +28,7 @@ VGMSTREAM* init_vgmstream_adp_wildfire(STREAMFILE* sf) {
vgmstream->loop_start_sample = read_s32le(0x08,sf);
vgmstream->loop_end_sample = vgmstream->num_samples;
vgmstream->coding_type = coding_DVI_IMA_int;
vgmstream->coding_type = coding_DVI_IMA_mono;
vgmstream->layout_type = layout_none;
vgmstream->meta_type = meta_ADP_WILDFIRE;

View File

@ -15,7 +15,7 @@ VGMSTREAM* init_vgmstream_ads(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00,sf,"SShd"))
goto fail;
return NULL;
/* .ads: actual extension
* .ss2: demuxed videos (fake?)
@ -25,15 +25,15 @@ VGMSTREAM* init_vgmstream_ads(STREAMFILE* sf) {
* .800: Mobile Suit Gundam: The One Year War (PS2)
* .sdl: Innocent Life: A Futuristic Harvest Moon (Special Edition) (PS2) */
if (!check_extensions(sf, "ads,ss2,pcm,adx,,800,sdl"))
goto fail;
return NULL;
if (read_u32le(0x04,sf) != 0x18 && /* standard header size */
read_u32le(0x04,sf) != 0x20 && /* True Fortune (PS2) */
read_u32le(0x04,sf) != get_streamfile_size(sf) - 0x08) /* Katamari Damacy videos */
goto fail;
return NULL;
if (!is_id32be(0x20,sf,"SSbd"))
goto fail;
return NULL;
/* base values (a bit unorderly since devs hack ADS too much and detection is messy) */
{
@ -52,7 +52,7 @@ VGMSTREAM* init_vgmstream_ads(STREAMFILE* sf) {
if (sample_rate == 12000 && interleave == 0x200) {
sample_rate = 48000;
interleave = 0x40;
coding_type = coding_DVI_IMA_int;
coding_type = coding_DVI_IMA_mono;
/* should try to detect IMA data but it's not so easy, this works ok since
* no known games use these settings, videos normally are 48000/24000hz */
}
@ -281,7 +281,7 @@ VGMSTREAM* init_vgmstream_ads(STREAMFILE* sf) {
case coding_PSX:
vgmstream->num_samples = ps_bytes_to_samples(stream_size, channels);
break;
case coding_DVI_IMA_int:
case coding_DVI_IMA_mono:
vgmstream->num_samples = ima_bytes_to_samples(stream_size, channels);
break;
default:

View File

@ -206,7 +206,7 @@ VGMSTREAM* init_vgmstream_aifc(STREAMFILE* sf) {
break;
case 0x41445034: /* "ADP4" */
coding_type = coding_DVI_IMA_int;
coding_type = coding_DVI_IMA_mono;
if (channels != 1) break; /* don't know how stereo DVI is laid out */
break;

View File

@ -6,10 +6,10 @@ VGMSTREAM* init_vgmstream_apple_caff(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset = 0, chunk_offset;
size_t file_size, data_size = 0;
int loop_flag, channel_count = 0, sample_rate = 0;
int loop_flag, channels = 0, sample_rate = 0;
int found_desc = 0 /*, found_pakt = 0*/, found_data = 0;
uint32_t codec = 0 /*, codec_flags = 0*/;
uint32_t codec = 0, codec_flags = 0;
uint32_t bytes_per_packet = 0, samples_per_packet = 0, channels_per_packet = 0, bits_per_sample = 0;
int valid_samples = 0 /*, priming_samples = 0, unused_samples = 0*/;
@ -17,7 +17,7 @@ VGMSTREAM* init_vgmstream_apple_caff(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00,sf, "caff"))
return NULL;
if (read_32bitBE(0x04,sf) != 0x00010000) /* version/flags */
if (read_u32be(0x04,sf) != 0x00010000) /* version/flags */
return NULL;
if (!check_extensions(sf, "caf"))
return NULL;
@ -43,12 +43,12 @@ VGMSTREAM* init_vgmstream_apple_caff(STREAMFILE* sf) {
sample_rate = (int)(*sample_double);
}
codec = read_32bitBE(chunk_offset+0x08, sf);
//codec_flags = read_32bitBE(chunk_offset+0x0c, streamFile);
bytes_per_packet = read_32bitBE(chunk_offset+0x10, sf);
samples_per_packet = read_32bitBE(chunk_offset+0x14, sf);
channels_per_packet = read_32bitBE(chunk_offset+0x18, sf);
bits_per_sample = read_32bitBE(chunk_offset+0x1C, sf);
codec = read_u32be(chunk_offset+0x08, sf);
codec_flags = read_u32be(chunk_offset+0x0c, sf);
bytes_per_packet = read_u32be(chunk_offset+0x10, sf);
samples_per_packet = read_u32be(chunk_offset+0x14, sf);
channels_per_packet = read_u32be(chunk_offset+0x18, sf);
bits_per_sample = read_u32be(chunk_offset+0x1C, sf);
break;
case 0x70616b74: /* "pakt" */
@ -56,8 +56,8 @@ VGMSTREAM* init_vgmstream_apple_caff(STREAMFILE* sf) {
//packets_table_size = (uint32_t)read_u64be(chunk_offset+0x00,streamFile); /* 0 for constant bitrate */
valid_samples = (uint32_t)read_u64be(chunk_offset+0x08,sf);
//priming_samples = read_32bitBE(chunk_offset+0x10,streamFile); /* encoder delay samples */
//unused_samples = read_32bitBE(chunk_offset+0x14,streamFile); /* footer samples */
//priming_samples = read_u32be(chunk_offset+0x10,streamFile); /* encoder delay samples */
//unused_samples = read_u32be(chunk_offset+0x14,streamFile); /* footer samples */
break;
case 0x64617461: /* "data" */
@ -83,11 +83,11 @@ VGMSTREAM* init_vgmstream_apple_caff(STREAMFILE* sf) {
loop_flag = 0;
channel_count = channels_per_packet;
channels = channels_per_packet;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
vgmstream = allocate_vgmstream(channels, loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_CAFF;
@ -97,12 +97,19 @@ VGMSTREAM* init_vgmstream_apple_caff(STREAMFILE* sf) {
case 0x6C70636D: /* "lpcm" */
vgmstream->num_samples = valid_samples;
if (!vgmstream->num_samples)
vgmstream->num_samples = pcm_bytes_to_samples(data_size, channel_count, bits_per_sample);
vgmstream->num_samples = pcm_bytes_to_samples(data_size, channels, bits_per_sample);
//todo check codec_flags for BE/LE, signed/etc
if (bits_per_sample == 8) {
vgmstream->coding_type = coding_PCM8;
}
else if (bits_per_sample == 16) {
if (codec_flags == 0)
vgmstream->coding_type = coding_PCM16BE; // Treasure Story (iOS)-fertilize_crop
else if (codec_flags == 2)
vgmstream->coding_type = coding_PCM16LE; // Katamari Amore (iOS), Soul Tamer Kiki HD (iOS)
else
goto fail;
}
else {
goto fail;
}
@ -114,7 +121,7 @@ VGMSTREAM* init_vgmstream_apple_caff(STREAMFILE* sf) {
case 0x696D6134: /* "ima4" [Vectros (iOS), Dragon Quest (iOS)] */
vgmstream->num_samples = valid_samples;
if (!vgmstream->num_samples) /* rare [Endless Fables 2 (iOS) */
vgmstream->num_samples = apple_ima4_bytes_to_samples(data_size, channel_count);
vgmstream->num_samples = apple_ima4_bytes_to_samples(data_size, channels);
vgmstream->coding_type = coding_APPLE_IMA4;
vgmstream->layout_type = layout_interleave;

118
src/meta/axhd.c Normal file
View File

@ -0,0 +1,118 @@
#include "meta.h"
#include "../coding/coding.h"
#include "../util/meta_utils.h"
/* AXHD - Anges Studios bank format [Red Dead Revolver (Xbox), Spy Hunter 2 (Xbox)] */
VGMSTREAM* init_vgmstream_axhd(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
/* checks */
if (!is_id32be(0x00,sf, "AXHD"))
return NULL;
if (read_u8(0x04, sf) != 0x82) //version?
return NULL;
/* .xhd+xbd: from bigfiles */
if (!check_extensions(sf, "xhd"))
return NULL;
meta_header_t h = {
.meta = meta_AXHD,
};
h.target_subsong = sf->stream_index;
if (h.target_subsong == 0)
h.target_subsong = 1;
// bank format somewhat like hd+bd from the PS2 versions
int codec = 0;
uint32_t table1_offset = 0x05;
// base sections (typically only 1)
int sections = read_u8(table1_offset,sf);
table1_offset += 0x01;
for (int i = 0; i < sections; i++) {
uint32_t table2_offset = read_u16le(table1_offset, sf);
table1_offset += 0x02;
// entries per section (usually 1 per subsong)
uint32_t subsections = read_u8(table2_offset, sf);
// 01: flags?
table2_offset += 0x01 + 0x04;
for (int j = 0; j < subsections; j++) {
uint32_t table3_offset = read_u16le(table2_offset, sf);
table2_offset += 0x02;
int sounds = read_u8(table3_offset, sf);
// 01: flags?
// 05: subflags?
table3_offset += 0x01 + 0x04 + 0x02;
for (int k = 0; k < sounds; k++) {
uint32_t sound_offset = read_u16le(table3_offset, sf);
table3_offset += 0x02;
h.total_subsongs++;
if (h.target_subsong != h.total_subsongs)
continue;
h.stream_offset = read_u32le(sound_offset + 0x00, sf);
// 04: flags (volume/pitch related?) + info?
int fmt_size = read_u8(sound_offset + 0x21, sf);
h.stream_size = read_u32le(sound_offset + 0x22, sf);
if (fmt_size == 0) { //dummy entry
codec = 0;
h.channels = 1;
h.sample_rate = 44100;
continue;
}
VGM_LOG("%x: %x + %x\n", sound_offset, h.stream_offset, h.stream_size );
// fmt
codec = read_u16le(sound_offset + 0x26, sf);
h.channels = read_u16le(sound_offset + 0x28, sf);
h.sample_rate = read_s32le(sound_offset + 0x2a, sf);
// 2e: average bitrate
// 32: block size
// 34: bits
//TODO: this format repeats streams offsets for different entries
}
}
}
switch (codec) {
case 0x00:
h.coding = coding_SILENCE;
h.layout = layout_none;
h.num_samples = h.sample_rate;
break;
case 0x01:
h.coding = coding_PCM16LE;
h.interleave = 0x02;
h.layout = layout_interleave;
h.num_samples = pcm16_bytes_to_samples(h.stream_size, h.channels);
break;
case 0x69:
h.coding = coding_XBOX_IMA;
h.layout = layout_none;
h.num_samples = xbox_ima_bytes_to_samples(h.stream_size, h.channels);
break;
default:
goto fail;
}
h.open_stream = true;
h.has_subsongs = true;
h.sf_head = sf;
h.sf_body = open_streamfile_by_ext(sf,"xbd");
if (!h.sf_body) goto fail;
vgmstream = alloc_metastream(&h);
close_streamfile(h.sf_body);
return vgmstream;
fail:
close_streamfile(h.sf_body);
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -227,7 +227,7 @@ static VGMSTREAM* init_vgmstream_bxwav(STREAMFILE* sf, bxwav_type_t type) {
break;
case 0x03:
vgmstream->coding_type = coding_IMA_int; // 3DS eShop applet (3DS)
vgmstream->coding_type = coding_IMA_mono; // 3DS eShop applet (3DS)
/* hist is read below */
break;

View File

@ -34,7 +34,7 @@ VGMSTREAM * init_vgmstream_dc_idvi(STREAMFILE *streamFile) {
vgmstream->loop_end_sample = vgmstream->num_samples;
vgmstream->meta_type = meta_DC_IDVI;
vgmstream->coding_type = coding_DVI_IMA_int;
vgmstream->coding_type = coding_DVI_IMA_mono;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x400;
if (vgmstream->interleave_block_size)

View File

@ -29,18 +29,20 @@ VGMSTREAM* init_vgmstream_ffmpeg(STREAMFILE* sf) {
/* no checks */
//if (!check_extensions(sf, "..."))
// goto fail;
// return NULL;
/* don't try to open headers and other mini files */
if (get_streamfile_size(sf) <= 0x1000)
goto fail;
return NULL;
// many PSP rips have poorly demuxed videos with a failty RIFF, allow for now
#if 0
/* reject some formats handled elsewhere (better fail and check there than let buggy FFmpeg take over) */
if (check_extensions(sf, "at3"))
goto fail;
#endif
uint32_t id = read_u32be(0x00, sf);
// rejected FSB may play as wonky .mp3
if ((id & 0xFFFFFF00) == get_id32be("FSB\0"))
return NULL;
// typically incorrectly extracted files with padding, best handle in riff.c that reads loops points
if (id == get_id32be("RIFF") && (read_u16le(0x14, sf) == 0x0270 || check_extensions(sf, "at3")))
return NULL;
if (target_subsong == 0) target_subsong = 1;

View File

@ -4,7 +4,7 @@
#include "fsb_interleave_streamfile.h"
typedef enum { MPEG, XBOX_IMA, FSB_IMA, PSX, XMA1, XMA2, DSP, CELT, PCM8, PCM8U, PCM16LE, PCM16BE } fsb_codec_t;
typedef enum { MPEG, XBOX_IMA, FSB_IMA, PSX, XMA1, XMA2, DSP, CELT, PCM8, PCM8U, PCM16LE, PCM16BE, SILENCE } fsb_codec_t;
typedef struct {
/* main header */
uint32_t id;
@ -36,7 +36,7 @@ typedef struct {
bool loop_flag;
off_t stream_offset;
uint32_t stream_offset;
fsb_codec_t codec;
} fsb_header_t;
@ -82,10 +82,13 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
read_string(vgmstream->stream_name, fsb.name_size + 1, fsb.name_offset, sf);
switch(fsb.codec) {
#ifdef VGM_USE_MPEG
case MPEG: { /* FSB4: Shatter (PS3), Way of the Samurai 3/4 (PS3) */
case MPEG: { /* FSB3: Spider-man 3 (PC/PS3), Rise of the Argonauts (PC), FSB4: Shatter (PS3), Way of the Samurai 3/4 (PS3) */
mpeg_custom_config cfg = {0};
cfg.fsb_padding = fsb.mpeg_padding; /* frames are sometimes padded for alignment */
cfg.fsb_padding = fsb.mpeg_padding; // frames are sometimes padded for alignment
cfg.data_size = fsb.stream_offset + fsb.stream_size;
vgmstream->codec_data = init_mpeg_custom(sf, fsb.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_FSB, &cfg);
if (!vgmstream->codec_data) goto fail;
@ -188,20 +191,25 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
}
#endif
case PCM8: /* no games known */
case PCM8U: /* FSB4: Crash Time 4: The Syndicate (X360) */
case PCM8: /* no known games */
case PCM8U: /* FSB4: Crash Time 4: The Syndicate (X360), Zoombinis (PC) */
vgmstream->coding_type = (fsb.codec == PCM8U) ? coding_PCM8_U : coding_PCM8;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x1;
break;
case PCM16LE: /* FSB4: Rocket Knight (PC), Another Century's Episode R (PS3), Toy Story 3 (Wii) */
case PCM16BE: /* FSB4: SpongeBob's Truth or Square (X360) */
case PCM16LE: /* FSB2: Hot Wheels World Race (PC)-bigfile-sfx, FSB3: Bee Movie (Wii), FSB4: Rocket Knight (PC), Another Century's Episode R (PS3), Toy Story 3 (Wii) */
case PCM16BE: /* FSB4: SpongeBob's Truth or Square (X360), Crash Time 4: The Syndicate (X360) */
vgmstream->coding_type = (fsb.codec == PCM16BE) ? coding_PCM16BE : coding_PCM16LE;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x2;
break;
case SILENCE: /* special case for broken MPEG */
vgmstream->coding_type = coding_SILENCE;
vgmstream->layout_type = layout_none;
break;
default:
goto fail;
}
@ -220,7 +228,7 @@ fail:
static layered_layout_data* build_layered_fsb_celt(STREAMFILE* sf, fsb_header_t* fsb, bool is_new_lib) {
layered_layout_data* data = NULL;
STREAMFILE* temp_sf = NULL;
int i, layers = (fsb->channels+1) / 2;
int layers = (fsb->channels+1) / 2;
/* init layout */
@ -228,7 +236,7 @@ static layered_layout_data* build_layered_fsb_celt(STREAMFILE* sf, fsb_header_t*
if (!data) goto fail;
/* open each layer subfile (1/2ch CELT streams: 2ch+2ch..+1ch or 2ch+2ch..+2ch) */
for (i = 0; i < layers; i++) {
for (int i = 0; i < layers; i++) {
int layer_channels = (i+1 == layers && fsb->channels % 2 == 1)
? 1 : 2; /* last layer can be 1/2ch */
@ -464,7 +472,7 @@ static bool parse_fsb(fsb_header_t* fsb, STREAMFILE* sf) {
if (fsb->id == get_id32be("FSB2")) {
fsb->meta_type = meta_FSB2;
fsb->base_header_size = 0x10;
fsb->sample_header_min = 0x40; /* guessed */
fsb->sample_header_min = 0x40;
}
else if (fsb->id == get_id32be("FSB3")) {
fsb->meta_type = meta_FSB3;
@ -528,7 +536,6 @@ static bool parse_fsb(fsb_header_t* fsb, STREAMFILE* sf) {
/* XMA basic headers have extra data [Forza Motorsport 3 (X360)] */
if (fsb->mode & FSOUND_XMA) {
VGM_LOG("h=%x\n", (uint32_t)header_offset);
// 0x08: flags? (0x00=none?, 0x20=standard)
// 0x0c: sample related? (may be 0 with no seek table)
// 0x10: low number (may be 0 with no seek table)
@ -613,6 +620,26 @@ static bool parse_fsb(fsb_header_t* fsb, STREAMFILE* sf) {
/* XOR encryption for some FSB4, though the flag is only seen after decrypting */
//;VGM_ASSERT(fsb->flags & FMOD_FSB_SOURCE_ENCRYPTED, "FSB ENCRYPTED found\n");
// rare FSB3 have odd cases [Rise of the Argonauts (PC)]
if (fsb->codec == MPEG && fsb->version == FMOD_FSB_VERSION_3_1) {
uint32_t mpeg_id = read_u32be(fsb->stream_offset, sf);
if ((mpeg_id & 0xFFFFFF00) == get_id32be("ID3\0")) {
// starts with ID3, probably legal but otherwise not seen (stripped?): Lykas_Atalanta_Join_DLG.fsb, Support_Of_The_Gods*.fsb
uint32_t tag_size = mpeg_get_tag_size(sf, fsb->stream_offset, mpeg_id); // always 0x1000, has 'PeakLevel' info
fsb->stream_offset += tag_size;
fsb->stream_size -= tag_size;
}
// completely empty MPEG, probably works by chance with OG decoder ignoring bad data: DLG_Lycomedes_Statue_*.fsb
if (mpeg_id == 0) {
fsb->codec = SILENCE;
}
// rarely sets more samples than data, must clamp reads to avoid spilling into next subsong: Player_Death_DLG.fsb, Lykas_Atalanta_Join_DLG.fsb
// probably a bug as samples don't seem to match MPEG's 'Info' headers and can be both bigger and smaller than loop_end
}
return true;
fail:
return false;

View File

@ -42,22 +42,22 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
fsb5_header fsb5 = {0};
uint32_t offset;
int target_subsong = sf->stream_index;
int i;
/* checks */
if (!is_id32be(0x00,sf, "FSB5"))
goto fail;
return NULL;
/* .fsb: standard
* .snd: Alchemy engine (also Unity) */
if (!check_extensions(sf,"fsb,snd"))
goto fail;
* .snd: Alchemy engine (also Unity)
* .fsb.ps3: Guacamelee! (PS3) */
if (!check_extensions(sf,"fsb,snd,ps3"))
return NULL;
/* v0 is rare, seen in Tales from Space (Vita) */
fsb5.version = read_u32le(0x04,sf);
if (fsb5.version != 0x00 && fsb5.version != 0x01)
goto fail;
return NULL;
fsb5.total_subsongs = read_u32le(0x08,sf);
fsb5.sample_header_size = read_u32le(0x0C,sf);
@ -81,7 +81,7 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
if ((fsb5.sample_header_size + fsb5.name_table_size + fsb5.sample_data_size + fsb5.base_header_size) != get_streamfile_size(sf)) {
vgm_logi("FSB5: wrong size, expected %x + %x + %x + %x vs %x (re-rip)\n", fsb5.sample_header_size, fsb5.name_table_size, fsb5.sample_data_size, fsb5.base_header_size, (uint32_t)get_streamfile_size(sf));
goto fail;
return NULL;
}
if (target_subsong == 0) target_subsong = 1;
@ -90,7 +90,7 @@ VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) {
/* find target stream header and data offset, and read all needed values for later use
* (reads one by one as the size of a single stream header is variable) */
offset = fsb5.base_header_size;
for (i = 0; i < fsb5.total_subsongs; i++) {
for (int i = 0; i < fsb5.total_subsongs; i++) {
uint32_t stream_header_size = 0;
uint32_t data_offset = 0;
uint64_t sample_mode;

View File

@ -169,9 +169,9 @@ VGMSTREAM* init_vgmstream_genh(STREAMFILE *sf) {
else {
vgmstream->layout_type = layout_interleave;
if (coding == coding_DVI_IMA)
coding = coding_DVI_IMA_int;
coding = coding_DVI_IMA_mono;
if (coding == coding_IMA)
coding = coding_IMA_int;
coding = coding_IMA_mono;
if (coding == coding_AICA)
coding = coding_AICA_int;
}
@ -180,8 +180,8 @@ VGMSTREAM* init_vgmstream_genh(STREAMFILE *sf) {
if (!genh.interleave && (
coding == coding_PSX ||
coding == coding_PSX_badflags ||
coding == coding_IMA_int ||
coding == coding_DVI_IMA_int ||
coding == coding_IMA_mono ||
coding == coding_DVI_IMA_mono ||
coding == coding_SDX2_int) ) {
goto fail;
}

View File

@ -1509,7 +1509,7 @@ static const hcakey_info hcakey_list[] = {
// DRAGON BALL: Sparking! ZERO (multi)
{13238534807163085345u}, // B7B8B9442F99A221
// TOUHOU GENSOU MAHJONG (PC) [demo and release]
// TOUHOU GENSOU MAHJONG (PC)
{7757726886}, // 00000001CE6584A6
// NARUTO X BORUTO NINJA VOLTAGE (Android)
@ -1523,6 +1523,10 @@ static const hcakey_info hcakey_list[] = {
// Tales of Graces f Remastered (PC)
{51485416730473395}, // 00B6E9B6B75533B3
// Freedom Wars Remastered (Switch)
{3258660547165106863}, // 2D391680A55B32AF
};
#endif

View File

@ -761,7 +761,7 @@ VGMSTREAM * init_vgmstream_ao(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_apc(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_wv2(STREAMFILE *streamFile);
VGMSTREAM* init_vgmstream_wav2(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_xau_konami(STREAMFILE *streamFile);
@ -1026,4 +1026,6 @@ VGMSTREAM* init_vgmstream_i3ds(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_skex(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_axhd(STREAMFILE* sf);
#endif

View File

@ -16,7 +16,7 @@ VGMSTREAM* init_vgmstream_msf(STREAMFILE* sf) {
// "MSF" + n.n version:
// - 0x01: Megazone 23: Aoi Garland (PS3)
// - 0x02: Switchball (PS3)
// - 0x30 ('0'): ?
// - 0x30 ('0'): Saints Row 2 (PS3)
// - 0x35 ('5'): SDKs
// - 0x43 ('C'): latest/most common
@ -49,7 +49,8 @@ VGMSTREAM* init_vgmstream_msf(STREAMFILE* sf) {
* 0x10 often goes with 0x01 but not always (Castlevania HoD); Malicious PS3 uses flag 0x2 instead */
loop_flag = (flags != 0xffffffff) && ((flags & 0x01) || (flags & 0x02));
/* loop offset markers (marker N @ 0x18 + N*(4+4), but in practice only marker 0 is used) */
/* loop offset markers: marker N = 0x18 + N * (0x08), but in practice only marker 0 is used
* (Saints Row 2 saves original filename in 0x28) */
if (loop_flag) {
loop_start = read_u32be(0x18,sf);
loop_end = read_u32be(0x1C,sf); /* loop duration */

View File

@ -91,7 +91,7 @@ VGMSTREAM* init_vgmstream_musx(STREAMFILE* sf) {
vgmstream->loop_start_sample = musx.loop_start / 4; /* weird but needed */
vgmstream->loop_end_sample = musx.loop_end / 4;
vgmstream->coding_type = coding_DVI_IMA_int;
vgmstream->coding_type = coding_DVI_IMA_mono;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x01;
break;

View File

@ -29,7 +29,7 @@ VGMSTREAM* init_vgmstream_myspd(STREAMFILE* sf) {
vgmstream->sample_rate = read_s32be(0x04,sf);
vgmstream->meta_type = meta_MYSPD;
vgmstream->coding_type = coding_IMA_int;
vgmstream->coding_type = coding_IMA_mono;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = channel_size;

View File

@ -31,7 +31,7 @@ VGMSTREAM * init_vgmstream_nds_hwas(STREAMFILE *streamFile) {
vgmstream->loop_start_sample = ima_bytes_to_samples(read_32bitLE(0x10,streamFile), channel_count); //assumed, always 0
vgmstream->loop_end_sample = ima_bytes_to_samples(read_32bitLE(0x18,streamFile), channel_count);
vgmstream->coding_type = coding_IMA_int;
vgmstream->coding_type = coding_IMA_mono;
vgmstream->layout_type = layout_blocked_hwas;
vgmstream->full_block_size = read_32bitLE(0x04,streamFile); /* usually 0x2000, 0x4000 or 0x8000 */

View File

@ -32,7 +32,7 @@ VGMSTREAM * init_vgmstream_nds_rrds(STREAMFILE *streamFile) {
}
vgmstream->meta_type = meta_NDS_RRDS;
vgmstream->coding_type = coding_IMA_int;
vgmstream->coding_type = coding_IMA_mono;
vgmstream->layout_type = layout_none;
{

View File

@ -1,285 +1,298 @@
#include "meta.h"
#include "../coding/coding.h"
#include <string.h>
#include <ctype.h>
static int get_loops_nwainfo_ini(STREAMFILE *sf, int *p_loop_flag, int32_t *p_loop_start);
static int get_loops_gameexe_ini(STREAMFILE *sf, int *p_loop_flag, int32_t *p_loop_start, int32_t *p_loop_end);
/* NWA - Visual Art's streams [Air (PC), Clannad (PC)] */
VGMSTREAM* init_vgmstream_nwa(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
int channel_count, loop_flag = 0;
int32_t loop_start_sample = 0, loop_end_sample = 0;
int nwainfo_ini_found = 0, gameexe_ini_found = 0;
int compression_level;
/* checks */
if (!check_extensions(sf, "nwa"))
goto fail;
channel_count = read_16bitLE(0x00,sf);
if (channel_count != 1 && channel_count != 2) goto fail;
/* check if we're using raw pcm */
if ( read_32bitLE(0x08,sf)==-1 || /* compression level */
read_32bitLE(0x10,sf)==0 || /* block count */
read_32bitLE(0x18,sf)==0 || /* compressed data size */
read_32bitLE(0x20,sf)==0 || /* block size */
read_32bitLE(0x24,sf)==0 ) { /* restsize */
compression_level = -1;
} else {
compression_level = read_32bitLE(0x08,sf);
}
/* loop points come from external files */
nwainfo_ini_found = get_loops_nwainfo_ini(sf, &loop_flag, &loop_start_sample);
gameexe_ini_found = !nwainfo_ini_found && get_loops_gameexe_ini(sf, &loop_flag, &loop_start_sample, &loop_end_sample);
start_offset = 0x2c;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = read_32bitLE(0x04,sf);
vgmstream->num_samples = read_32bitLE(0x1c,sf) / channel_count;
switch(compression_level) {
case -1:
switch (read_16bitLE(0x02,sf)) {
case 8:
vgmstream->coding_type = coding_PCM8;
vgmstream->interleave_block_size = 0x01;
break;
case 16:
vgmstream->coding_type = coding_PCM16LE;
vgmstream->interleave_block_size = 0x02;
break;
default:
goto fail;
}
vgmstream->layout_type = layout_interleave;
break;
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
vgmstream->coding_type = coding_NWA;
vgmstream->layout_type = layout_none;
vgmstream->codec_data = init_nwa(sf);
if (!vgmstream->codec_data) goto fail;
break;
default:
goto fail;
break;
}
if (nwainfo_ini_found) {
vgmstream->meta_type = meta_NWA_NWAINFOINI;
if (loop_flag) {
vgmstream->loop_start_sample = loop_start_sample;
vgmstream->loop_end_sample = vgmstream->num_samples;
}
} else if (gameexe_ini_found) {
vgmstream->meta_type = meta_NWA_GAMEEXEINI;
if (loop_flag) {
vgmstream->loop_start_sample = loop_start_sample;
vgmstream->loop_end_sample = loop_end_sample;
}
} else {
vgmstream->meta_type = meta_NWA;
}
if (!vgmstream_open_stream(vgmstream,sf,start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}
/* try to locate NWAINFO.INI in the same directory */
static int get_loops_nwainfo_ini(STREAMFILE *sf, int *p_loop_flag, int32_t *p_loop_start) {
STREAMFILE *sf_loop;
char namebase[PATH_LIMIT];
const char * ext;
int length;
int found;
off_t offset;
size_t file_size;
off_t found_off = -1;
int loop_flag = 0;
int32_t loop_start_sample = 0;
sf_loop = open_streamfile_by_filename(sf, "NWAINFO.INI");
if (!sf_loop) goto fail;
get_streamfile_filename(sf,namebase,PATH_LIMIT);
/* ini found, try to find our name */
ext = filename_extension(namebase);
length = ext - 1 - namebase;
file_size = get_streamfile_size(sf_loop);
for (found = 0, offset = 0; !found && offset < file_size; offset++) {
off_t suboffset;
/* Go for an n*m search 'cause it's easier than building an
* FSA for the search string. Just wanted to make the point that
* I'm not ignorant, just lazy. */
for (suboffset = offset;
suboffset < file_size &&
suboffset-offset < length &&
read_8bit(suboffset,sf_loop) == namebase[suboffset-offset];
suboffset++) {
/* skip */
}
if (suboffset-offset==length && read_8bit(suboffset,sf_loop)==0x09) { /* tab */
found = 1;
found_off = suboffset+1;
}
}
/* if found file name in INI */
if (found) {
char loopstring[9] = {0};
if (read_streamfile((uint8_t*)loopstring,found_off,8,sf_loop) == 8) {
loop_start_sample = atol(loopstring);
if (loop_start_sample > 0)
loop_flag = 1;
}
}
*p_loop_flag = loop_flag;
*p_loop_start = loop_start_sample;
close_streamfile(sf_loop);
return 1;
fail:
close_streamfile(sf_loop);
return 0;
}
/* try to locate Gameexe.ini in the same directory */
static int get_loops_gameexe_ini(STREAMFILE* sf, int* p_loop_flag, int32_t* p_loop_start, int32_t* p_loop_end) {
STREAMFILE*sf_loop;
char namebase[PATH_LIMIT];
const char* ext;
int length;
int found;
off_t offset;
off_t file_size;
off_t found_off = -1;
int loop_flag = 0;
int32_t loop_start_sample = 0, loop_end_sample = 0;
sf_loop = open_streamfile_by_filename(sf, "Gameexe.ini");
if (!sf_loop) goto fail;
get_streamfile_filename(sf,namebase,PATH_LIMIT);
/* ini found, try to find our name */
ext = filename_extension(namebase);
length = ext-1-namebase;
file_size = get_streamfile_size(sf_loop);
/* According to the official documentation of RealLiveMax (the public version of RealLive), format of line is:
* #DSTRACK = 00000000 - eeeeeeee - ssssssss = "filename" = "alias for game script"
* ^22 ^33 ^45 ^57?
*
* Original text from the documentation (written in Japanese) is:
* ;
* ;
* ; 99999999
* ;
* ;=========================================================================================================
* ; - - = =
* #DSTRACK = 00000000 - 01896330 - 00088270 = "b_manuke" = "b_manuke"
* #DSTRACK = 00000000 - 01918487 - 00132385 = "c_happy" = "c_happy"
*/
for (found = 0, offset = 0; !found && offset<file_size; offset++) {
off_t suboffset;
uint8_t buf[10];
if (read_8bit(offset,sf_loop)!='#') continue;
if (read_streamfile(buf,offset+1,10,sf_loop)!=10) break;
if (memcmp("DSTRACK = ",buf,10)) continue;
if (read_8bit(offset+44,sf_loop)!='\"') continue;
for (suboffset = offset+45;
suboffset < file_size &&
suboffset-offset-45 < length &&
tolower(read_8bit(suboffset,sf_loop)) == tolower(namebase[suboffset-offset-45]);
suboffset++) {
/* skip */
}
if (suboffset-offset-45==length && read_8bit(suboffset,sf_loop)=='\"') { /* tab */
found = 1;
found_off = offset+22; /* loop end */
}
}
if (found) {
char loopstring[9] = {0};
int start_ok = 0, end_ok = 0;
int32_t total_samples = read_32bitLE(0x1c,sf) / read_16bitLE(0x00,sf);
if (read_streamfile((uint8_t*)loopstring,found_off,8,sf_loop)==8)
{
if (!memcmp("99999999",loopstring,8)) {
loop_end_sample = total_samples;
}
else {
loop_end_sample = atol(loopstring);
}
end_ok = 1;
}
if (read_streamfile((uint8_t*)loopstring,found_off+11,8,sf_loop)==8)
{
if (!memcmp("99999999",loopstring,8)) {
/* not ok to start at last sample,
* don't set start_ok flag */
}
else if (!memcmp("00000000",loopstring,8)) {
/* loops from the start aren't really loops */
}
else {
loop_start_sample = atol(loopstring);
start_ok = 1;
}
}
if (start_ok && end_ok) loop_flag = 1;
} /* if found file name in INI */
*p_loop_flag = loop_flag;
*p_loop_start = loop_start_sample;
*p_loop_end = loop_end_sample;
close_streamfile(sf_loop);
return 1;
fail:
close_streamfile(sf_loop);
return 0;
}
#include "meta.h"
#include "../coding/coding.h"
#include <string.h>
#include <ctype.h>
static int get_loops_nwainfo_ini(STREAMFILE *sf, int *p_loop_flag, int32_t *p_loop_start);
static int get_loops_gameexe_ini(STREAMFILE *sf, int *p_loop_flag, int32_t *p_loop_start, int32_t *p_loop_end);
/* .NWA - Visual Art's streams [Air (PC), Clannad (PC)] */
VGMSTREAM* init_vgmstream_nwa(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
int channels, sample_rate, bps, loop_flag = 0;
int32_t loop_start_sample = 0, loop_end_sample = 0;
bool nwainfo_ini_found = false, gameexe_ini_found = false;
int compression_level;
/* checks */
if (!check_extensions(sf, "nwa"))
return NULL;
channels = read_s16le(0x00,sf);
bps = read_s16le(0x02,sf);
sample_rate = read_s32le(0x04,sf);
if (channels != 1 && channels != 2)
return NULL;
if (bps != 0 && bps != 8 && bps != 16)
return NULL;
if (sample_rate < 8000 || sample_rate > 48000) //unsure if can go below 44100
return NULL;
/* check if we're using raw pcm */
if ( read_s32le(0x08,sf) == -1 || /* compression level */
read_s32le(0x10,sf) == 0 || /* block count */
read_s32le(0x18,sf) == 0 || /* compressed data size */
read_s32le(0x20,sf) == 0 || /* block size */
read_s32le(0x24,sf) == 0 ) { /* restsize */
compression_level = -1;
}
else {
compression_level = read_s32le(0x08,sf);
}
if (compression_level > 5)
return NULL;
/* loop points come from external files */
nwainfo_ini_found = get_loops_nwainfo_ini(sf, &loop_flag, &loop_start_sample);
gameexe_ini_found = !nwainfo_ini_found && get_loops_gameexe_ini(sf, &loop_flag, &loop_start_sample, &loop_end_sample);
start_offset = 0x2c;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels,loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = read_s32le(0x04,sf);
vgmstream->num_samples = read_s32le(0x1c,sf) / channels;
switch(compression_level) {
case -1:
switch (bps) {
case 8:
vgmstream->coding_type = coding_PCM8;
vgmstream->interleave_block_size = 0x01;
break;
case 16:
vgmstream->coding_type = coding_PCM16LE;
vgmstream->interleave_block_size = 0x02;
break;
default:
goto fail;
}
vgmstream->layout_type = layout_interleave;
break;
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
vgmstream->coding_type = coding_NWA;
vgmstream->layout_type = layout_none;
vgmstream->codec_data = init_nwa(sf);
if (!vgmstream->codec_data) goto fail;
break;
default:
goto fail;
break;
}
if (nwainfo_ini_found) {
vgmstream->meta_type = meta_NWA_NWAINFOINI;
if (loop_flag) {
vgmstream->loop_start_sample = loop_start_sample;
vgmstream->loop_end_sample = vgmstream->num_samples;
}
}
else if (gameexe_ini_found) {
vgmstream->meta_type = meta_NWA_GAMEEXEINI;
if (loop_flag) {
vgmstream->loop_start_sample = loop_start_sample;
vgmstream->loop_end_sample = loop_end_sample;
}
}
else {
vgmstream->meta_type = meta_NWA;
}
if (!vgmstream_open_stream(vgmstream,sf,start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}
/* try to locate NWAINFO.INI in the same directory */
static int get_loops_nwainfo_ini(STREAMFILE *sf, int *p_loop_flag, int32_t *p_loop_start) {
STREAMFILE *sf_loop;
char namebase[PATH_LIMIT];
const char * ext;
int length;
int found;
off_t offset;
size_t file_size;
off_t found_off = -1;
int loop_flag = 0;
int32_t loop_start_sample = 0;
sf_loop = open_streamfile_by_filename(sf, "NWAINFO.INI");
if (!sf_loop) goto fail;
get_streamfile_filename(sf,namebase,PATH_LIMIT);
/* ini found, try to find our name */
ext = filename_extension(namebase);
length = ext - 1 - namebase;
file_size = get_streamfile_size(sf_loop);
for (found = 0, offset = 0; !found && offset < file_size; offset++) {
off_t suboffset;
/* Go for an n*m search 'cause it's easier than building an
* FSA for the search string. Just wanted to make the point that
* I'm not ignorant, just lazy. */
for (suboffset = offset;
suboffset < file_size &&
suboffset-offset < length &&
read_8bit(suboffset,sf_loop) == namebase[suboffset-offset];
suboffset++) {
/* skip */
}
if (suboffset-offset==length && read_8bit(suboffset,sf_loop)==0x09) { /* tab */
found = 1;
found_off = suboffset+1;
}
}
/* if found file name in INI */
if (found) {
char loopstring[9] = {0};
if (read_streamfile((uint8_t*)loopstring,found_off,8,sf_loop) == 8) {
loop_start_sample = atol(loopstring);
if (loop_start_sample > 0)
loop_flag = 1;
}
}
*p_loop_flag = loop_flag;
*p_loop_start = loop_start_sample;
close_streamfile(sf_loop);
return 1;
fail:
close_streamfile(sf_loop);
return 0;
}
/* try to locate Gameexe.ini in the same directory */
static int get_loops_gameexe_ini(STREAMFILE* sf, int* p_loop_flag, int32_t* p_loop_start, int32_t* p_loop_end) {
STREAMFILE*sf_loop;
char namebase[PATH_LIMIT];
const char* ext;
int length;
int found;
off_t offset;
off_t file_size;
off_t found_off = -1;
int loop_flag = 0;
int32_t loop_start_sample = 0, loop_end_sample = 0;
sf_loop = open_streamfile_by_filename(sf, "Gameexe.ini");
if (!sf_loop) goto fail;
get_streamfile_filename(sf,namebase,PATH_LIMIT);
/* ini found, try to find our name */
ext = filename_extension(namebase);
length = ext-1-namebase;
file_size = get_streamfile_size(sf_loop);
/* According to the official documentation of RealLiveMax (the public version of RealLive), format of line is:
* #DSTRACK = 00000000 - eeeeeeee - ssssssss = "filename" = "alias for game script"
* ^22 ^33 ^45 ^57?
*
* Original text from the documentation (written in Japanese) is:
* ;
* ;
* ; 99999999
* ;
* ;=========================================================================================================
* ; - - = =
* #DSTRACK = 00000000 - 01896330 - 00088270 = "b_manuke" = "b_manuke"
* #DSTRACK = 00000000 - 01918487 - 00132385 = "c_happy" = "c_happy"
*/
for (found = 0, offset = 0; !found && offset<file_size; offset++) {
off_t suboffset;
uint8_t buf[10];
if (read_8bit(offset,sf_loop)!='#') continue;
if (read_streamfile(buf,offset+1,10,sf_loop)!=10) break;
if (memcmp("DSTRACK = ",buf,10)) continue;
if (read_8bit(offset+44,sf_loop)!='\"') continue;
for (suboffset = offset+45;
suboffset < file_size &&
suboffset-offset-45 < length &&
tolower(read_8bit(suboffset,sf_loop)) == tolower(namebase[suboffset-offset-45]);
suboffset++) {
/* skip */
}
if (suboffset-offset-45==length && read_8bit(suboffset,sf_loop)=='\"') { /* tab */
found = 1;
found_off = offset+22; /* loop end */
}
}
if (found) {
char loopstring[9] = {0};
int start_ok = 0, end_ok = 0;
int32_t total_samples = read_32bitLE(0x1c,sf) / read_16bitLE(0x00,sf);
if (read_streamfile((uint8_t*)loopstring,found_off,8,sf_loop)==8)
{
if (!memcmp("99999999",loopstring,8)) {
loop_end_sample = total_samples;
}
else {
loop_end_sample = atol(loopstring);
}
end_ok = 1;
}
if (read_streamfile((uint8_t*)loopstring,found_off+11,8,sf_loop)==8)
{
if (!memcmp("99999999",loopstring,8)) {
/* not ok to start at last sample,
* don't set start_ok flag */
}
else if (!memcmp("00000000",loopstring,8)) {
/* loops from the start aren't really loops */
}
else {
loop_start_sample = atol(loopstring);
start_ok = 1;
}
}
if (start_ok && end_ok) loop_flag = 1;
} /* if found file name in INI */
*p_loop_flag = loop_flag;
*p_loop_start = loop_start_sample;
*p_loop_end = loop_end_sample;
close_streamfile(sf_loop);
return 1;
fail:
close_streamfile(sf_loop);
return 0;
}

View File

@ -16,23 +16,24 @@ VGMSTREAM* init_vgmstream_ogg_opus(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00,sf, "OggS"))
goto fail;
return NULL;
/* .opus: standard, .lopus: fake extension for plugins
* .ogg: less common, .logg: same
* .bgm: Utawarerumono: Mask of Truth (PC) */
if (!check_extensions(sf, "opus,lopus,ogg,logg,bgm"))
goto fail;
* .bgm: Utawarerumono: Mask of Truth (PC)
* .oga: niconico app (Switch) */
if (!check_extensions(sf, "opus,lopus,ogg,logg,bgm,oga"))
return NULL;
/* see: https://tools.ietf.org/html/rfc7845.html */
start_offset = 0x00;
/* parse 1st page: opus head */
if (!get_ogg_page_size(sf, start_offset, &data_offset, &page_size))
goto fail;
return NULL;
if (!is_id32be(data_offset+0x00,sf, "Opus") ||
!is_id32be(data_offset+0x04,sf, "Head"))
goto fail;
return NULL;
/* 0x01: version 1, fixed */
channel_count = read_u8(data_offset+0x09,sf);
/* 0x0A: skip samples */

View File

@ -161,7 +161,6 @@ fail:
static int _init_vgmstream_ogg_vorbis_tests(STREAMFILE* sf, ogg_vorbis_io_config_data* cfg, ogg_vorbis_meta_info_t* ovmi) {
/* standard */
if (is_id32be(0x00,sf, "OggS")) {
@ -171,9 +170,12 @@ static int _init_vgmstream_ogg_vorbis_tests(STREAMFILE* sf, ogg_vorbis_io_config
* .acm: Planescape Torment Enhanced Edition (PC)
* .sod: Zone 4 (PC)
* .msa: Metal Slug Attack (Mobile)
* .bin/lbin: Devil May Cry 3: Special Edition (PC) */
if (check_extensions(sf,"ogg,logg,adx,rof,acm,sod,msa,bin,lbin"))
return 1;
* .bin/lbin: Devil May Cry 3: Special Edition (PC)
* .oga: Aqua Panic! (PC), Heroes of Annihilated Empires (PC)-pre-demuxed movie audio
* .ogs: Exodus from the Earth (PC)
* .ogv: Tenshi no Hane wo Fumanaide (PC) */
if (check_extensions(sf,"ogg,logg,adx,rof,acm,sod,msa,bin,lbin,oga,ogs,ogv"))
return true;
/* ignore others to allow stuff like .sngw */
}
@ -266,7 +268,7 @@ static int _init_vgmstream_ogg_vorbis_tests(STREAMFILE* sf, ogg_vorbis_io_config
/* encrypted [Adventure Field 4 (PC)] */
if (read_u32be(0x00,sf) == 0x4F756F71) {
ovmi->decryption_callback = at4_ogg_decryption_callback; //TODO replace with generic descryption?
ovmi->decryption_callback = at4_ogg_decryption_callback; //TODO replace with generic decryption?
if (!check_extensions(sf,"ogg,logg"))
goto fail;

View File

@ -1,53 +1,53 @@
#include "meta.h"
#include "../util.h"
/* SND - Might and Magic games [Warriors of M&M (PS2), Heroes of M&M: Quest for the DragonBone Staff (PS2)] */
VGMSTREAM * init_vgmstream_ps2_snd(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
size_t data_size;
int loop_flag, channel_count;
/* checks */
if (!check_extensions(streamFile, "snd"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x53534E44) /* "SSND" */
goto fail;
start_offset = read_32bitLE(0x04,streamFile)+0x08;
data_size = get_streamfile_size(streamFile) - start_offset;
loop_flag = 1; /* force full Loop */
channel_count = read_16bitLE(0x0a,streamFile);
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = (uint16_t)read_16bitLE(0x0e,streamFile);
vgmstream->num_samples = read_32bitLE(0x16,streamFile);
vgmstream->loop_start_sample = 0;
vgmstream->loop_end_sample = vgmstream->num_samples;
vgmstream->meta_type = meta_PS2_SND;
if (read_8bit(0x08,streamFile)==1) {
vgmstream->coding_type = coding_DVI_IMA_int; /* Warriors of M&M DragonBone */
}
else {
vgmstream->coding_type = coding_PCM16LE; /* Heroes of M&M */
}
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = (uint16_t)read_16bitLE(0x12,streamFile);
if (vgmstream->interleave_block_size)
vgmstream->interleave_last_block_size = (data_size % (vgmstream->interleave_block_size*vgmstream->channels)) / vgmstream->channels;
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}
#include "meta.h"
#include "../util.h"
/* SND - Might and Magic games [Warriors of M&M (PS2), Heroes of M&M: Quest for the DragonBone Staff (PS2)] */
VGMSTREAM * init_vgmstream_ps2_snd(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
size_t data_size;
int loop_flag, channel_count;
/* checks */
if (!check_extensions(streamFile, "snd"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x53534E44) /* "SSND" */
goto fail;
start_offset = read_32bitLE(0x04,streamFile)+0x08;
data_size = get_streamfile_size(streamFile) - start_offset;
loop_flag = 1; /* force full Loop */
channel_count = read_16bitLE(0x0a,streamFile);
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = (uint16_t)read_16bitLE(0x0e,streamFile);
vgmstream->num_samples = read_32bitLE(0x16,streamFile);
vgmstream->loop_start_sample = 0;
vgmstream->loop_end_sample = vgmstream->num_samples;
vgmstream->meta_type = meta_PS2_SND;
if (read_8bit(0x08,streamFile)==1) {
vgmstream->coding_type = coding_DVI_IMA_mono; /* Warriors of M&M DragonBone */
}
else {
vgmstream->coding_type = coding_PCM16LE; /* Heroes of M&M */
}
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = (uint16_t)read_16bitLE(0x12,streamFile);
if (vgmstream->interleave_block_size)
vgmstream->interleave_last_block_size = (data_size % (vgmstream->interleave_block_size*vgmstream->channels)) / vgmstream->channels;
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -110,7 +110,7 @@ VGMSTREAM* init_vgmstream_rage_aud(STREAMFILE* sf) {
#endif
case 0x0400: /* PC */
vgmstream->coding_type = coding_IMA_int;
vgmstream->coding_type = coding_IMA_mono;
vgmstream->layout_type = aud.is_streamed ? layout_blocked_rage_aud : layout_none;
vgmstream->full_block_size = aud.block_chunk;
break;

View File

@ -66,7 +66,7 @@ VGMSTREAM* init_vgmstream_sadl(STREAMFILE* sf) {
switch(flags & 0xf0) { /* possibly >> 6? (0/1/2) */
case 0x00: /* Luminous Arc (DS) (non-int IMA? all files are mono though) */
case 0x70: /* Ni no Kuni (DS), Professor Layton and the Curious Village (DS), Soma Bringer (DS) */
vgmstream->coding_type = coding_IMA_int;
vgmstream->coding_type = coding_IMA_mono;
vgmstream->num_samples = ima_bytes_to_samples(data_size, channels);
vgmstream->loop_start_sample = ima_bytes_to_samples(loop_start, channels);

View File

@ -29,7 +29,7 @@ VGMSTREAM* init_vgmstream_sat_dvi(STREAMFILE* sf) {
vgmstream->loop_start_sample = read_s32be(0x0C,sf);
vgmstream->loop_end_sample = read_s32be(0x08,sf);
vgmstream->coding_type = coding_DVI_IMA_int;
vgmstream->coding_type = coding_DVI_IMA_mono;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x4;
vgmstream->meta_type = meta_SAT_DVI;

View File

@ -82,7 +82,7 @@ VGMSTREAM* init_vgmstream_stma(STREAMFILE* sf) {
dsp_read_hist_be(vgmstream, sf, 0x60, 0x60);
}
else { /* DVI IMA ADPCM (Red Dead Revolver, Midnight Club 2) */
vgmstream->coding_type = coding_DVI_IMA_int;
vgmstream->coding_type = coding_DVI_IMA_mono;
/* 'interleave not' reliable, strange values and rarely needs 0x80 */
vgmstream->interleave_block_size = (interleave == 0xc000) ? 0x80 : 0x40;

View File

@ -60,7 +60,7 @@ VGMSTREAM* init_vgmstream_swav(STREAMFILE* sf) {
bits_per_sample = 16;
break;
case 2:
coding_type = coding_IMA_int;
coding_type = coding_IMA_mono;
bits_per_sample = 4;
break;
default:
@ -79,7 +79,7 @@ VGMSTREAM* init_vgmstream_swav(STREAMFILE* sf) {
vgmstream->loop_end_sample = loop_end * 32 / bits_per_sample + vgmstream->loop_start_sample;
}
if (coding_type == coding_IMA_int) {
if (coding_type == coding_IMA_mono) {
/* handle IMA frame header */
vgmstream->loop_start_sample -= 32 / bits_per_sample;
vgmstream->loop_end_sample -= 32 / bits_per_sample;

View File

@ -371,9 +371,9 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
else {
vgmstream->layout_type = layout_interleave;
if (coding == coding_DVI_IMA)
coding = coding_DVI_IMA_int;
coding = coding_DVI_IMA_mono;
if (coding == coding_IMA)
coding = coding_IMA_int;
coding = coding_IMA_mono;
if (coding == coding_AICA)
coding = coding_AICA_int;
}
@ -383,8 +383,8 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) {
coding == coding_PSX ||
coding == coding_PSX_badflags ||
coding == coding_HEVAG ||
coding == coding_IMA_int ||
coding == coding_DVI_IMA_int ||
coding == coding_IMA_mono ||
coding == coding_DVI_IMA_mono ||
coding == coding_SDX2_int ||
coding == coding_AICA_int) ) {
goto fail;

View File

@ -60,7 +60,7 @@ VGMSTREAM* init_vgmstream_ubi_apm(STREAMFILE* sf) {
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_UBI_APM;
vgmstream->coding_type = coding_DVI_IMA_int;
vgmstream->coding_type = coding_DVI_IMA_mono;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x01;
vgmstream->sample_rate = sample_rate;

View File

@ -1198,7 +1198,7 @@ static VGMSTREAM* init_vgmstream_ubi_sb_base(ubi_sb_header* sb, STREAMFILE* sf_h
/* APM is a full format though most fields are repeated from .bnm
* see ubi_apm.c for documentation */
vgmstream->coding_type = coding_DVI_IMA_int;
vgmstream->coding_type = coding_DVI_IMA_mono;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x01;

49
src/meta/wav2.c Normal file
View File

@ -0,0 +1,49 @@
#include "meta.h"
#include "../coding/coding.h"
/* WAV2 - from Infogrames North America(?) games [Slave Zero (PC), Outcast (PC)] */
VGMSTREAM* init_vgmstream_wav2(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
size_t data_size;
int loop_flag, channels, sample_rate;
/* checks */
if (!is_id32be(0x00, sf, "WAV2"))
return NULL;
if (!check_extensions(sf,"wv2"))
return NULL;
// 04: offset?
// 08: pcm samples?
channels = read_u16le(0x0c,sf);
// 0e: bps
sample_rate = read_s32le(0x10,sf);
// 14: average bitrate?
data_size = read_u32le(0x18, sf);
loop_flag = 0;
start_offset = 0x1c;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channels,loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_WAV2;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = ima_bytes_to_samples(data_size, channels); /* also 0x18 */
vgmstream->coding_type = coding_DVI_IMA_mono;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0xFA;
vgmstream->interleave_last_block_size = (data_size % (vgmstream->interleave_block_size * channels)) / channels;
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -118,7 +118,7 @@ VGMSTREAM* init_vgmstream_wave(STREAMFILE* sf) {
}
case 0x03: //IMA (DS uses codec 02 for IMA, common; 3DS: uses 03 but not seen)
vgmstream->coding_type = coding_IMA_int;
vgmstream->coding_type = coding_IMA_mono;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = interleave;

View File

@ -85,7 +85,7 @@ VGMSTREAM * init_vgmstream_wave_segmented(STREAMFILE *sf) {
data->segments[i]->sample_rate = sample_rate;
data->segments[i]->meta_type = meta_WAVE;
data->segments[i]->coding_type = coding_IMA_int;
data->segments[i]->coding_type = coding_IMA_mono;
data->segments[i]->layout_type = layout_none;
data->segments[i]->num_samples = segment_samples;

View File

@ -76,7 +76,7 @@ VGMSTREAM* init_vgmstream_ws_aud(STREAMFILE* sf) {
break;
case 0x63: /* IMA ADPCM [Blade Runner (PC)] */
vgmstream->coding_type = coding_IMA_int;
vgmstream->coding_type = coding_IMA_mono;
break;
default:
goto fail;

View File

@ -1,43 +0,0 @@
#include "meta.h"
#include "../coding/coding.h"
/* WAV2 - from Infrogrames North America games [Slave Zero (PC) (PS2)] */
VGMSTREAM * init_vgmstream_wv2(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
size_t data_size;
int loop_flag, channel_count;
/* checks */
if ( !check_extensions(streamFile,"wv2") )
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x57415632) /* "WAV2" */
goto fail;
start_offset = 0x1c;
data_size = get_streamfile_size(streamFile) - start_offset;
channel_count = read_8bit(0x0c,streamFile);
loop_flag = 0;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
vgmstream->meta_type = meta_WV2;
vgmstream->sample_rate = read_32bitLE(0x10,streamFile);
vgmstream->num_samples = ima_bytes_to_samples(data_size,channel_count); /* also 0x18 */
vgmstream->coding_type = coding_DVI_IMA_int;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0xFA;
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -357,7 +357,7 @@ init_vgmstream_t init_vgmstream_functions[] = {
init_vgmstream_aif_asobo,
init_vgmstream_ao,
init_vgmstream_apc,
init_vgmstream_wv2,
init_vgmstream_wav2,
init_vgmstream_xau_konami,
init_vgmstream_derf,
init_vgmstream_utk,
@ -517,6 +517,7 @@ init_vgmstream_t init_vgmstream_functions[] = {
init_vgmstream_i3ds,
init_vgmstream_sdbs,
init_vgmstream_skex,
init_vgmstream_axhd,
/* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */
init_vgmstream_agsc,

View File

@ -60,9 +60,9 @@ typedef enum {
coding_EA_XAS_V1, /* Electronic Arts EA-XAS ADPCM v1 */
coding_IMA, /* IMA ADPCM (stereo or mono, low nibble first) */
coding_IMA_int, /* IMA ADPCM (mono/interleave, low nibble first) */
coding_IMA_mono, /* IMA ADPCM (mono, low nibble first) */
coding_DVI_IMA, /* DVI IMA ADPCM (stereo or mono, high nibble first) */
coding_DVI_IMA_int, /* DVI IMA ADPCM (mono/interleave, high nibble first) */
coding_DVI_IMA_mono, /* DVI IMA ADPCM (mono, high nibble first) */
coding_CAMELOT_IMA,
coding_SNDS_IMA, /* Heavy Iron Studios .snds IMA ADPCM */
coding_QD_IMA,
@ -608,7 +608,7 @@ typedef enum {
meta_AIF_ASOBO, /* Ratatouille (PC) */
meta_AO, /* Cloudphobia (PC) */
meta_APC, /* MegaRace 3 (PC) */
meta_WV2, /* Slave Zero (PC) */
meta_WAV2,
meta_XAU_KONAMI, /* Yu-Gi-Oh - The Dawn of Destiny (Xbox) */
meta_DERF, /* Stupid Invaders (PC) */
meta_SADF,
@ -716,6 +716,7 @@ typedef enum {
meta_PPHD,
meta_XABP,
meta_I3DS,
meta_AXHD,
} meta_t;