mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-02-21 12:51:57 +01:00
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:
commit
70d4ac8821
@ -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
|
||||
|
17
cli/vjson.h
17
cli/vjson.h
@ -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>
|
||||
|
41
doc/TXTP.md
41
doc/TXTP.md
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
195
src/base/api_libsf_cache.c
Normal 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;
|
||||
}
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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?
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
118
src/meta/axhd.c
Normal 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;
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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;
|
||||
|
||||
{
|
||||
|
583
src/meta/nwa.c
583
src/meta/nwa.c
@ -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:
|
||||
* ; ■BGMの登録:DirectSound
|
||||
* ;(※必要ない場合は登録しないで下さい。)
|
||||
* ; 終了位置の設定が 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:
|
||||
* ; ■BGMの登録:DirectSound
|
||||
* ;(※必要ない場合は登録しないで下さい。)
|
||||
* ; 終了位置の設定が 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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
49
src/meta/wav2.c
Normal 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;
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user