mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-12 01:30:49 +01:00
Merge pull request #1556 from bnnm/api-misc
- Minor tweak in TXTH path handling - API (WIP) - cleanup
This commit is contained in:
commit
3b4a175472
2
Makefile
2
Makefile
@ -300,4 +300,4 @@ clean:
|
||||
$(MAKE) -C xmplay clean
|
||||
$(MAKE) -C ext_libs clean
|
||||
|
||||
.PHONY: clean buildfullrelease buildrelease sourceball bin vgmstream-cli vgmstream_cli vgmstream123 winamp xmplay version
|
||||
.PHONY: clean buildfullrelease buildrelease sourceball bin vgmstream-cli vgmstream_cli vgmstream123 api_example winamp xmplay version
|
||||
|
@ -442,6 +442,7 @@ bool VgmstreamPlugin::play(const char * filename, VFSFile & file) {
|
||||
render_vgmstream(buffer, to_do, vgmstream);
|
||||
|
||||
write_audio(buffer, to_do * sizeof(short) * output_channels);
|
||||
//TODO: detect how many written
|
||||
decode_pos_samples += to_do;
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,6 @@ $(TARGET_EXT_LIBS):
|
||||
$(MAKE) -C ../ext_libs $@
|
||||
|
||||
clean:
|
||||
$(RMF) $(OUTPUT_CLI) $(OUTPUT_123) $(OUTPUT_123)
|
||||
$(RMF) $(OUTPUT_CLI) $(OUTPUT_123) $(OUTPUT_API)
|
||||
|
||||
.PHONY: clean vgmstream_cli libvgmstream.a $(TARGET_EXT_LIBS)
|
||||
|
@ -1,112 +1,410 @@
|
||||
#if 0
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "../src/api.h"
|
||||
|
||||
|
||||
static void usage(const char* progname) {
|
||||
fprintf(stderr, "API test v%08x\n"
|
||||
"Usage: %s <infile>\n"
|
||||
, libvgmstream_get_version()
|
||||
, progname
|
||||
);
|
||||
}
|
||||
|
||||
static FILE* get_output_file(const char* filename) {
|
||||
char out_filename[0x7FFF];
|
||||
snprintf(out_filename, sizeof(out_filename), "%s.pcm", filename);
|
||||
|
||||
FILE* outfile = fopen(out_filename, "wb");
|
||||
if (!outfile) {
|
||||
fprintf(stderr, "failed to open %s for output\n", out_filename);
|
||||
}
|
||||
return outfile;
|
||||
}
|
||||
|
||||
static libvgmstream_streamfile_t* get_streamfile(const char* filename) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* simplistic example of vgmstream's API
|
||||
* for something a bit more featured see vgmstream-cli
|
||||
*/
|
||||
int main(int argc, char** argv) {
|
||||
int err;
|
||||
FILE* outfile = NULL;
|
||||
const char* infile;
|
||||
|
||||
if (argc != 2) {
|
||||
usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
infile = argv[1];
|
||||
|
||||
|
||||
// main init
|
||||
libvgmstream_t* lib = libvgmstream_init();
|
||||
if (!lib) return EXIT_FAILURE;
|
||||
|
||||
|
||||
// set default config
|
||||
libvgmstream_config_t cfg = {
|
||||
.loop_count = 2.0,
|
||||
.fade_time = 10.0,
|
||||
};
|
||||
libvgmstream_setup(lib, &cfg);
|
||||
|
||||
|
||||
// open target file
|
||||
libvgmstream_options_t options = {
|
||||
.sf = get_streamfile(infile)
|
||||
};
|
||||
err = libvgmstream_open(lib, &options);
|
||||
if (err < 0) goto fail;
|
||||
|
||||
|
||||
// external SF is not needed after _open
|
||||
libvgmstream_streamfile_close(options.sf);
|
||||
|
||||
|
||||
// output file
|
||||
outfile = get_output_file(infile);
|
||||
if (!outfile) goto fail;
|
||||
|
||||
|
||||
// play file and do something with decoded samples
|
||||
while (true) {
|
||||
int pos;
|
||||
|
||||
if (lib->decoder->done)
|
||||
break;
|
||||
|
||||
// get current samples
|
||||
err = libvgmstream_play(lib);
|
||||
if (err < 0) goto fail;
|
||||
|
||||
fwrite(lib->decoder->buf, sizeof(uint8_t), lib->decoder->buf_bytes, outfile);
|
||||
|
||||
pos = (int)libvgmstream_play_position(lib);
|
||||
printf("\rpos: %d", pos);
|
||||
fflush(stdout);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
// close current streamfile before opening new ones, optional
|
||||
//libvgmstream_close(lib);
|
||||
|
||||
// process done
|
||||
libvgmstream_free(lib);
|
||||
fclose(outfile);
|
||||
|
||||
printf("done\n");
|
||||
return EXIT_SUCCESS;
|
||||
fail:
|
||||
// process failed
|
||||
libvgmstream_free(lib);
|
||||
fclose(outfile);
|
||||
|
||||
printf("failed!\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
#endif
|
||||
#include "../src/api.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "../src/base/api_internal.h"
|
||||
|
||||
|
||||
static void usage(const char* progname) {
|
||||
fprintf(stderr, "Usage: %s <infile>\n"
|
||||
, progname
|
||||
);
|
||||
}
|
||||
|
||||
static FILE* get_output_file(const char* filename) {
|
||||
char out_filename[0x7FFF];
|
||||
snprintf(out_filename, sizeof(out_filename), "%s.pcm", filename);
|
||||
|
||||
FILE* outfile = fopen(out_filename, "wb");
|
||||
if (!outfile) {
|
||||
fprintf(stderr, "failed to open %s for output\n", out_filename);
|
||||
}
|
||||
return outfile;
|
||||
}
|
||||
|
||||
static libvgmstream_streamfile_t* get_streamfile(const char* filename) {
|
||||
return libvgmstream_streamfile_from_filename(filename);
|
||||
}
|
||||
|
||||
static int api_example(const char* infile) {
|
||||
VGM_STEP();
|
||||
int err;
|
||||
FILE* outfile = NULL;
|
||||
|
||||
bool fill_test;
|
||||
int pcm16_samples;
|
||||
int pcm16_bytes;
|
||||
short* pcm16 = NULL;
|
||||
|
||||
|
||||
// main init
|
||||
libvgmstream_t* lib = libvgmstream_init();
|
||||
if (!lib) return EXIT_FAILURE;
|
||||
|
||||
|
||||
// set default config
|
||||
libvgmstream_config_t cfg = {
|
||||
//.loop_count = 1.0,
|
||||
//.fade_time = 10.0,
|
||||
.ignore_loop = true,
|
||||
};
|
||||
libvgmstream_setup(lib, &cfg);
|
||||
|
||||
|
||||
// open target file
|
||||
libvgmstream_options_t opt = {
|
||||
.libsf = get_streamfile(infile)
|
||||
};
|
||||
err = libvgmstream_open(lib, &opt);
|
||||
// external SF is not needed after _open
|
||||
libvgmstream_streamfile_close(opt.libsf);
|
||||
|
||||
if (err < 0) {
|
||||
printf("not a valid file\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// output file
|
||||
outfile = get_output_file(infile);
|
||||
if (!outfile) goto fail;
|
||||
|
||||
{
|
||||
char title[128];
|
||||
libvgmstream_title_t cfg = {
|
||||
.filename = infile,
|
||||
.remove_extension = true,
|
||||
};
|
||||
libvgmstream_get_title(lib, &cfg, title, sizeof(title));
|
||||
|
||||
printf("- format title:\n");
|
||||
printf("%s\n", title);
|
||||
}
|
||||
|
||||
{
|
||||
char describe[1024];
|
||||
libvgmstream_format_describe(lib, describe, sizeof(describe));
|
||||
|
||||
printf("- format describe:\n");
|
||||
printf("%s\n", describe);
|
||||
}
|
||||
|
||||
printf("- format info\n");
|
||||
printf("channels: %i\n", lib->format->channels);
|
||||
printf("sample rate: %i\n", lib->format->sample_rate);
|
||||
printf("codec: %s\n", lib->format->codec_name);
|
||||
printf("samples: %i\n", (int32_t)lib->format->sample_count);
|
||||
printf("\n");
|
||||
printf("- decoding: %i\n" , (int32_t)lib->format->play_samples);
|
||||
|
||||
|
||||
|
||||
fill_test = true;
|
||||
pcm16_samples = 512;
|
||||
pcm16_bytes = pcm16_samples * sizeof(short) * lib->format->channels;
|
||||
pcm16 = malloc(pcm16_bytes);
|
||||
if (!pcm16) goto fail;
|
||||
|
||||
// play file and do something with decoded samples
|
||||
while (true) {
|
||||
//int pos;
|
||||
void* buf;
|
||||
int buf_bytes = 0;
|
||||
|
||||
if (lib->decoder->done)
|
||||
break;
|
||||
|
||||
// get current samples
|
||||
if (fill_test) {
|
||||
err = libvgmstream_fill(lib, pcm16, pcm16_samples);
|
||||
if (err < 0) goto fail;
|
||||
|
||||
buf = pcm16;
|
||||
buf_bytes = err * sizeof(short) * lib->format->channels;
|
||||
}
|
||||
else {
|
||||
err = libvgmstream_play(lib);
|
||||
if (err < 0) goto fail;
|
||||
|
||||
buf = lib->decoder->buf;
|
||||
buf_bytes = lib->decoder->buf_bytes;
|
||||
}
|
||||
|
||||
fwrite(buf, sizeof(uint8_t), buf_bytes, outfile);
|
||||
|
||||
//pos = (int)libvgmstream_get_play_position(lib);
|
||||
//printf("\r- decode pos: %d", pos);
|
||||
//fflush(stdout);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
// close current streamfile before opening new ones, optional
|
||||
//libvgmstream_close(lib);
|
||||
|
||||
// process done
|
||||
libvgmstream_free(lib);
|
||||
fclose(outfile);
|
||||
free(pcm16);
|
||||
|
||||
printf("done\n");
|
||||
return EXIT_SUCCESS;
|
||||
fail:
|
||||
// process failed
|
||||
libvgmstream_free(lib);
|
||||
fclose(outfile);
|
||||
free(pcm16);
|
||||
|
||||
printf("failed!\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
static void test_lib_is_valid() {
|
||||
VGM_STEP();
|
||||
|
||||
bool expected, result;
|
||||
|
||||
expected = true;
|
||||
result = libvgmstream_is_valid("test.adx", NULL);
|
||||
assert(expected == result);
|
||||
|
||||
expected = true;
|
||||
result = libvgmstream_is_valid("extensionless", NULL);
|
||||
assert(expected == result);
|
||||
|
||||
libvgmstream_valid_t cfg = {
|
||||
.reject_extensionless = true
|
||||
};
|
||||
expected = false;
|
||||
result = libvgmstream_is_valid("extensionless", &cfg);
|
||||
assert(expected == result);
|
||||
}
|
||||
|
||||
static void test_lib_extensions() {
|
||||
VGM_STEP();
|
||||
|
||||
const char** exts;
|
||||
size_t size = 0;
|
||||
|
||||
size = 0;
|
||||
exts = libvgmstream_get_extensions(&size);
|
||||
assert(exts != NULL && size > 100);
|
||||
|
||||
exts = libvgmstream_get_extensions(NULL);
|
||||
assert(exts == NULL);
|
||||
|
||||
size = 0;
|
||||
exts = libvgmstream_get_common_extensions(&size);
|
||||
assert(exts != NULL && size > 1 && size < 100);
|
||||
|
||||
exts = libvgmstream_get_common_extensions(NULL);
|
||||
assert(exts == NULL);
|
||||
}
|
||||
|
||||
static libvgmstream_streamfile_t* test_libsf_open() {
|
||||
VGM_STEP();
|
||||
|
||||
libvgmstream_streamfile_t* libsf = NULL;
|
||||
|
||||
libsf = libvgmstream_streamfile_from_filename("api.bin_wrong");
|
||||
assert(libsf == NULL);
|
||||
|
||||
libsf = libvgmstream_streamfile_from_filename("api.bin");
|
||||
assert(libsf != NULL);
|
||||
|
||||
return libsf;
|
||||
}
|
||||
|
||||
|
||||
static void test_libsf_read(libvgmstream_streamfile_t* libsf) {
|
||||
VGM_STEP();
|
||||
|
||||
int read;
|
||||
uint8_t buf[0x20];
|
||||
|
||||
read = libsf->read(libsf->user_data, buf, sizeof(buf));
|
||||
assert(read == sizeof(buf));
|
||||
for (int i = 0; i < sizeof(buf); i++) {
|
||||
assert(buf[i] == ((i + 0x00) & 0xFF));
|
||||
}
|
||||
|
||||
read = libsf->read(libsf->user_data, buf, sizeof(buf));
|
||||
assert(read == sizeof(buf));
|
||||
for (int i = 0; i < sizeof(buf); i++) {
|
||||
assert(buf[i] == ((i + 0x20) & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
static void test_libsf_seek_read(libvgmstream_streamfile_t* libsf) {
|
||||
VGM_STEP();
|
||||
|
||||
int read, res;
|
||||
uint8_t buf[0x20];
|
||||
|
||||
res = libsf->seek(libsf->user_data, 0x19BC0, 0);
|
||||
assert(res == 0);
|
||||
|
||||
read = libsf->read(libsf->user_data, buf, sizeof(buf));
|
||||
assert(read == sizeof(buf));
|
||||
for (int i = 0; i < sizeof(buf); i++) {
|
||||
assert(buf[i] == ((i + 0xC0) & 0xFF));
|
||||
}
|
||||
|
||||
res = libsf->seek(libsf->user_data, 0x7FFFFF, 0);
|
||||
assert(res == 0);
|
||||
|
||||
read = libsf->read(libsf->user_data, buf, sizeof(buf));
|
||||
assert(read == 0);
|
||||
}
|
||||
|
||||
static void test_libsf_size(libvgmstream_streamfile_t* libsf) {
|
||||
VGM_STEP();
|
||||
|
||||
int64_t size = libsf->get_size(libsf->user_data);
|
||||
assert(size == 0x20000);
|
||||
}
|
||||
|
||||
static void test_libsf_name(libvgmstream_streamfile_t* libsf) {
|
||||
VGM_STEP();
|
||||
|
||||
char name[128];
|
||||
libsf->get_name(libsf->user_data, name, sizeof(name));
|
||||
assert(strcmp(name, "api.bin") == 0);
|
||||
}
|
||||
|
||||
static void test_libsf_reopen(libvgmstream_streamfile_t* libsf) {
|
||||
VGM_STEP();
|
||||
|
||||
uint8_t buf[0x20];
|
||||
int read;
|
||||
|
||||
libvgmstream_streamfile_t* newsf = NULL;
|
||||
|
||||
newsf = libsf->open(libsf->user_data, "api2.bin_wrong");
|
||||
assert(newsf == NULL);
|
||||
|
||||
newsf = libsf->open(libsf->user_data, "api2.bin");
|
||||
assert(newsf != NULL);
|
||||
|
||||
read = newsf->read(newsf->user_data, buf, sizeof(buf));
|
||||
assert(read == sizeof(buf));
|
||||
assert(buf[0x10] == 0x10);
|
||||
|
||||
newsf->close(newsf);
|
||||
}
|
||||
|
||||
static void test_libsf_apisf(libvgmstream_streamfile_t* libsf) {
|
||||
VGM_STEP();
|
||||
|
||||
STREAMFILE* sf = open_api_streamfile(libsf);
|
||||
assert(sf != NULL);
|
||||
|
||||
int read;
|
||||
uint8_t buf[0x20];
|
||||
|
||||
read = read_streamfile(buf, 0xF0, sizeof(buf), sf);
|
||||
assert(read == sizeof(buf));
|
||||
for (int i = 0; i < sizeof(buf); i++) {
|
||||
assert(buf[i] == ((i + 0xF0) & 0xFF));
|
||||
}
|
||||
|
||||
size_t size = get_streamfile_size(sf);
|
||||
assert(size == 0x20000);
|
||||
|
||||
close_streamfile(sf);
|
||||
|
||||
}
|
||||
|
||||
|
||||
static void test_lib_streamfile() {
|
||||
VGM_STEP();
|
||||
|
||||
libvgmstream_streamfile_t* libsf = test_libsf_open();
|
||||
test_libsf_read(libsf);
|
||||
test_libsf_seek_read(libsf);
|
||||
test_libsf_size(libsf);
|
||||
test_libsf_name(libsf);
|
||||
test_libsf_reopen(libsf);
|
||||
test_libsf_apisf(libsf);
|
||||
|
||||
// close
|
||||
libsf->close(libsf);
|
||||
}
|
||||
|
||||
static void test_lib_tags() {
|
||||
VGM_STEP();
|
||||
|
||||
libvgmstream_streamfile_t* libsf = NULL;
|
||||
libvgmstream_tags_t* tags = NULL;
|
||||
bool more = false;
|
||||
|
||||
libsf = libvgmstream_streamfile_from_filename("sample_!tags.m3u");
|
||||
assert(libsf != NULL);
|
||||
|
||||
tags = libvgmstream_tags_init(libsf);
|
||||
assert(tags != NULL);
|
||||
|
||||
|
||||
libvgmstream_tags_find(tags, "filename1.adx");
|
||||
more = libvgmstream_tags_next_tag(tags);
|
||||
assert(more && strcmp(tags->key, "ARTIST") == 0 && strcmp(tags->val, "global artist") == 0);
|
||||
more = libvgmstream_tags_next_tag(tags);
|
||||
assert(more && strcmp(tags->key, "TITLE") == 0 && strcmp(tags->val, "filename1 title") == 0);
|
||||
more = libvgmstream_tags_next_tag(tags);
|
||||
assert(!more);
|
||||
|
||||
|
||||
libvgmstream_tags_find(tags, "filename2.adx");
|
||||
more = libvgmstream_tags_next_tag(tags);
|
||||
assert(more && strcmp(tags->key, "ARTIST") == 0 && strcmp(tags->val, "global artist") == 0);
|
||||
more = libvgmstream_tags_next_tag(tags);
|
||||
assert(more && strcmp(tags->key, "TITLE") == 0 && strcmp(tags->val, "filename2 title") == 0);
|
||||
more = libvgmstream_tags_next_tag(tags);
|
||||
assert(!more);
|
||||
|
||||
|
||||
libvgmstream_tags_find(tags, "filename3.adx");
|
||||
more = libvgmstream_tags_next_tag(tags);
|
||||
assert(more && strcmp(tags->key, "ARTIST") == 0 && strcmp(tags->val, "global artist") == 0);
|
||||
more = libvgmstream_tags_next_tag(tags);
|
||||
assert(!more);
|
||||
|
||||
|
||||
libvgmstream_tags_find(tags, "filename_incorrect.adx");
|
||||
more = libvgmstream_tags_next_tag(tags);
|
||||
assert(more && strcmp(tags->key, "ARTIST") == 0 && strcmp(tags->val, "global artist") == 0);
|
||||
more = libvgmstream_tags_next_tag(tags);
|
||||
assert(!more);
|
||||
|
||||
libvgmstream_tags_free(tags);
|
||||
libvgmstream_streamfile_close(libsf);
|
||||
}
|
||||
|
||||
|
||||
/* simplistic example of vgmstream's API
|
||||
* for something a bit more featured see vgmstream-cli
|
||||
*/
|
||||
int main(int argc, char** argv) {
|
||||
printf("API v%08x test\n", libvgmstream_get_version());
|
||||
|
||||
libvgmstream_log_t cfg = {
|
||||
.stdout_callback = true
|
||||
};
|
||||
libvgmstream_set_log(&cfg);
|
||||
|
||||
test_lib_is_valid();
|
||||
test_lib_extensions();
|
||||
test_lib_streamfile();
|
||||
test_lib_tags();
|
||||
|
||||
if (argc != 2) {
|
||||
usage(argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
api_example(argv[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
@ -901,7 +901,7 @@ static int write_file(VGMSTREAM* vgmstream, cli_config* cfg) {
|
||||
|
||||
render_vgmstream(buf, to_get, vgmstream);
|
||||
|
||||
swap_samples_le(buf, channels * to_get); /* write PC endian */
|
||||
swap_samples_le(buf, channels * to_get); /* change to WAV (LE) endian if PC is Big Endian */
|
||||
fwrite(buf, sizeof(sample_t), to_get * channels, outfile);
|
||||
/* should write infinitely until program kill */
|
||||
}
|
||||
@ -929,7 +929,7 @@ static int write_file(VGMSTREAM* vgmstream, cli_config* cfg) {
|
||||
render_vgmstream(buf, to_get, vgmstream);
|
||||
|
||||
if (!cfg->decode_only) {
|
||||
swap_samples_le(buf, channels * to_get); /* write PC endian */
|
||||
swap_samples_le(buf, channels * to_get); /* change to WAV (LE) endian if PC is Big Endian */
|
||||
fwrite(buf, sizeof(sample_t), to_get * channels, outfile);
|
||||
}
|
||||
}
|
||||
|
46
src/api.h
46
src/api.h
@ -2,14 +2,34 @@
|
||||
#define _API_H_
|
||||
#include "base/plugins.h" //TODO: to be removed
|
||||
|
||||
#if 0
|
||||
//#define LIBVGMSTREAM_ENABLE 1
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
/* vgmstream's public API
|
||||
* basic usage (also see api_example.c):
|
||||
* - libvgmstream_get_version() // if needed
|
||||
*
|
||||
* By default vgmstream behaves like a simple decoder (extract samples until stream end), but you can configure it
|
||||
* to loop N times or even downmix (since complex formats need those features). In other words, it also behaves
|
||||
* a bit like a player.
|
||||
*
|
||||
* It exposes multiple options and convenience functions beyond simple decoding mainly for various plugins,
|
||||
* since it was faster moving shared behavior to core rather than reimplementing every time.
|
||||
*
|
||||
* All this may make the API a bit twisted and coupled (sorry, tried my best), probably will improve later. Probably.
|
||||
*
|
||||
* Notes:
|
||||
* - vgmstream may dynamically allocate stuff as needed (not too much beyond some setup buffers, but varies per format)
|
||||
* - previously the only way to use vgmstream was accesing its internals. Now there is an API internals may change in the future
|
||||
* - some details described in the API may not happen at the moment (they are defined to consider internal changes)
|
||||
* - main reason it uses the slighly long-winded libvgmstream_* names is that internals use the vgmstream_* 'namespace'
|
||||
* - c-strings should be in UTF-8
|
||||
* - the API is still WIP and may be slightly buggy overall due to lack of time, to be improved later
|
||||
* - vgmstream's features are mostly stable, but this API may be tweaked from time to time (check API_VERSION)
|
||||
*
|
||||
* Basic usage (also see api_example.c):
|
||||
* - libvgmstream_get_version() // just in case
|
||||
* - libvgmstream_init(...) // base context
|
||||
* - libvgmstream_setup(...) // standard config
|
||||
* - libvgmstream_open(...) // check detected format
|
||||
* - libvgmstream_setup(...) // config if needed
|
||||
* - libvgmstream_open(...) // setup format
|
||||
* - libvgmstream_play(...) // main decode
|
||||
* - output samples + repeat libvgmstream_play until stream is done
|
||||
* - libvgmstream_free(...) // cleanup
|
||||
@ -33,14 +53,22 @@
|
||||
#endif
|
||||
|
||||
|
||||
/* Current API version. Only refers to the API itself, as changes related to formats/etc don't alter it.
|
||||
* vgmstream's features are mostly stable, while this API may change from time to time.
|
||||
* May change as well when related (such as api_streamfile.h) are changed. */
|
||||
/* Current API version.
|
||||
* - only refers to the API itself, as changes related to formats/etc don't alter this (since they are usually additive)
|
||||
* - 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
|
||||
|
||||
#include "api_main.h"
|
||||
/* returns API version in hex format: 0xMMmmpppp = MM-major, mm-minor, pppp-patch
|
||||
* - use when loading vgmstream as a dynamic library to ensure API/ABI compatibility
|
||||
*/
|
||||
LIBVGMSTREAM_API uint32_t libvgmstream_get_version(void);
|
||||
|
||||
|
||||
#include "api_decode.h"
|
||||
#include "api_helpers.h"
|
||||
#include "api_streamfile.h"
|
||||
#include "api_tags.h"
|
||||
|
||||
|
202
src/api_decode.h
Normal file
202
src/api_decode.h
Normal file
@ -0,0 +1,202 @@
|
||||
#ifndef _API_DECODE_H_
|
||||
#define _API_DECODE_H_
|
||||
#include "api.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
#include "api_streamfile.h"
|
||||
|
||||
|
||||
/* vgmstream's main (decode) API.
|
||||
*/
|
||||
|
||||
|
||||
/* 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_t;
|
||||
|
||||
/* current song info, may be copied around (values are info-only) */
|
||||
typedef struct {
|
||||
/* main (always set) */
|
||||
libvgmstream_sample_t sample_type; // output buffer's sample type
|
||||
int channels; // output channels
|
||||
int sample_rate; // output sample rate
|
||||
|
||||
int sample_size; // derived from sample_type (pcm16=2, float=4, etc)
|
||||
|
||||
/* extra info (may be 0 if not known or not relevant) */
|
||||
uint32_t channel_layout; // standard WAVE bitflags
|
||||
|
||||
int subsong_index; // 0 = none, N = loaded subsong N
|
||||
int subsong_count; // 0 = format has no concept of subsongs, N = has N subsongs (1 = format has subsongs, and only 1)
|
||||
|
||||
int input_channels; // original file's channels before downmixing (if any)
|
||||
//int interleave; // when file is interleaved
|
||||
//int interleave_first; // when file is interleaved
|
||||
//int interleave_last; // when file is interleaved
|
||||
//int frame_size; // when file has some configurable frame size
|
||||
|
||||
/* sample info (may not be used depending on config) */
|
||||
int64_t sample_count; // file's max samples (not final play duration)
|
||||
int64_t loop_start; // loop start sample
|
||||
int64_t loop_end; // loop end sample
|
||||
bool loop_flag; // if file loops; note that false + defined loops means looping was forcefully disabled
|
||||
|
||||
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)
|
||||
// ** may not be 100% accurate in some cases (must check decoder's 'done' flag rather than this count)
|
||||
// ** 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
|
||||
|
||||
/* 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
|
||||
// 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 (do not use to export)
|
||||
|
||||
int format_internal_id; // may be used when reopening subfiles or similar formats without checking other possible formats first
|
||||
// ** this value WILL change without warning between vgmstream versions/commits
|
||||
|
||||
} libvgmstream_format_t;
|
||||
|
||||
|
||||
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, meaning current _decode can't generate samples)
|
||||
int buf_bytes; // current buffer bytes (channels * sample_size * samples)
|
||||
|
||||
bool done; // flag when stream is done playing based on config; will still allow _play calls returning blank samples
|
||||
// ** note that with play_forever this flag is never set
|
||||
} libvgmstream_decoder_t;
|
||||
|
||||
|
||||
/* vgmstream context/handle */
|
||||
typedef struct {
|
||||
void* priv; // internal data
|
||||
|
||||
/* pointers for easier ABI compatibility */
|
||||
const libvgmstream_format_t* format; // current song info, updated on _open
|
||||
libvgmstream_decoder_t* decoder; // updated on each _decode call
|
||||
|
||||
} libvgmstream_t;
|
||||
|
||||
|
||||
|
||||
/* inits the vgmstream context
|
||||
* - returns NULL on error
|
||||
* - call libvgmstream_free when done.
|
||||
*/
|
||||
LIBVGMSTREAM_API libvgmstream_t* libvgmstream_init(void);
|
||||
|
||||
/* frees vgmstream context and any other internal stuff that may not be closed
|
||||
*/
|
||||
LIBVGMSTREAM_API void libvgmstream_free(libvgmstream_t* lib);
|
||||
|
||||
|
||||
/* configures how vgmstream behaves internally when playing a file */
|
||||
typedef struct {
|
||||
bool disable_config_override; // ignore forced (TXTP) config
|
||||
bool allow_play_forever; // must allow manually as some cases a TXTP may set loop forever but client may not handle it (ex. wave dumpers)
|
||||
|
||||
bool play_forever; // keeps looping forever (file must have loop_flag set)
|
||||
bool ignore_loop; // ignores loops points
|
||||
bool force_loop; // enables full loops (0..samples) if file doesn't have loop points
|
||||
bool really_force_loop; // forces full loops (0..samples) even if file has loop points
|
||||
bool ignore_fade; // don't fade after N loops and play remaning stream (for files with outros)
|
||||
|
||||
double loop_count; // target loops (values like 1.5 are ok)
|
||||
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
|
||||
// ** for players that can only handle N channels, but this type of downmixing is very simplistic and not recommended
|
||||
|
||||
bool force_pcm16; // forces output buffer to be remixed into PCM16
|
||||
|
||||
} libvgmstream_config_t;
|
||||
|
||||
/* pass default config, that will be applied to song on open
|
||||
* - invalid config or complex cases (ex. some TXTP) may ignore these settings.
|
||||
* - called without a song loaded (before _open or after _close), otherwise ignored.
|
||||
* - without config vgmstream will decode the current stream once
|
||||
*/
|
||||
LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_config_t* cfg);
|
||||
|
||||
|
||||
/* configures how vgmstream opens the format */
|
||||
typedef struct {
|
||||
libvgmstream_streamfile_t* libsf; // custom IO streamfile that provides reader info for vgmstream
|
||||
// ** not needed after _open and should be closed, as vgmstream re-opens its own SFs internally as needed
|
||||
|
||||
int subsong; // target subsong (1..N) or 0 = default/first
|
||||
// ** to check if a file has subsongs, _open first + check format->total_subsongs (then _open 2nd, 3rd, etc)
|
||||
|
||||
int format_internal_id; // force a format (for example when loading new subsong of the same archive)
|
||||
|
||||
int stereo_track; // forces vgmstream to decode one 2ch+2ch+2ch... 'track' and discard other channels, where 0 = disabled, 1..N = Nth track
|
||||
|
||||
} libvgmstream_options_t;
|
||||
|
||||
/* opens file based on config and prepares it to play if supported.
|
||||
* - returns < 0 on error (file not recognised, invalid subsong index, etc)
|
||||
* - will close currently loaded song if needed
|
||||
*/
|
||||
LIBVGMSTREAM_API int libvgmstream_open(libvgmstream_t* lib, libvgmstream_options_t* open_options);
|
||||
|
||||
#if 0
|
||||
/* opens file based on config and returns its file info
|
||||
* - returns < 0 on error (file not recognised, invalid subsong index, etc)
|
||||
* - equivalent to _open, but doesn't update the current loaded song / format and copies to passed struct
|
||||
* - may be used while current song is loaded/playing but you need to query next song with current config
|
||||
* - to play a new song don't call _open_info to check the format first, just call _open + check format afterwards
|
||||
* - in many cases the only way for vgmstream to get a file's format info is making it almost ready to play,
|
||||
* so this isn't any faster than _open
|
||||
*/
|
||||
LIBVGMSTREAM_API int libvgmstream_open_info(libvgmstream_t* lib, libvgmstream_options_t* options, libvgmstream_format_t* format);
|
||||
#endif
|
||||
|
||||
/* closes current song; may still use libvgmstream to open other songs
|
||||
*/
|
||||
LIBVGMSTREAM_API void libvgmstream_close(libvgmstream_t* lib);
|
||||
|
||||
|
||||
/* decodes next batch of samples
|
||||
* - vgmstream supplies its own buffer, updated on lib->decoder->* values (may change between calls)
|
||||
* - returns < 0 on error
|
||||
*/
|
||||
LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib);
|
||||
|
||||
/* Same as _play, but fills some external buffer (also updates lib->decoder->* values)
|
||||
* - returns < 0 on error, or N = number of filled samples.
|
||||
* - buf must be at least as big as channels * sample_size * buf_samples
|
||||
* - needs copying around from internal bufs so may be slightly slower; mainly for cases when you have buf constraints
|
||||
*/
|
||||
LIBVGMSTREAM_API int libvgmstream_fill(libvgmstream_t* lib, void* buf, int buf_samples);
|
||||
|
||||
/* Gets current position within the song.
|
||||
* - return < 0 on error (file not ready)
|
||||
*/
|
||||
LIBVGMSTREAM_API int64_t libvgmstream_get_play_position(libvgmstream_t* lib);
|
||||
|
||||
/* Seeks to absolute position. Will clamp incorrect values such as seeking before/past playable length.
|
||||
* - on play_forever may seek to any position
|
||||
*/
|
||||
LIBVGMSTREAM_API void libvgmstream_seek(libvgmstream_t* lib, int64_t sample);
|
||||
|
||||
/* Reset current song
|
||||
*/
|
||||
LIBVGMSTREAM_API void libvgmstream_reset(libvgmstream_t* lib);
|
||||
|
||||
#endif
|
||||
#endif
|
81
src/api_helpers.h
Normal file
81
src/api_helpers.h
Normal file
@ -0,0 +1,81 @@
|
||||
#ifndef _API_HELPERS_H_
|
||||
#define _API_HELPERS_H_
|
||||
#include "api.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
|
||||
/* vgmstream's helper stuff, for plugins.
|
||||
*/
|
||||
|
||||
|
||||
typedef enum {
|
||||
LIBVGMSTREAM_LOG_LEVEL_ALL = 0,
|
||||
LIBVGMSTREAM_LOG_LEVEL_DEBUG = 20,
|
||||
LIBVGMSTREAM_LOG_LEVEL_INFO = 30,
|
||||
LIBVGMSTREAM_LOG_LEVEL_NONE = 100,
|
||||
} libvgmstream_loglevel_t;
|
||||
|
||||
typedef struct {
|
||||
libvgmstream_loglevel_t level; // log level
|
||||
void (*callback)(int level, const char* str); // log callback
|
||||
bool stdout_callback; // use default log callback rather than user supplied
|
||||
} libvgmstream_log_t;
|
||||
|
||||
/* defines a global log callback, as vgmstream sometimes communicates format issues to the user.
|
||||
* - note that log is currently set globally rather than per libvgmstream_t
|
||||
*/
|
||||
LIBVGMSTREAM_API void libvgmstream_set_log(libvgmstream_log_t* cfg);
|
||||
|
||||
|
||||
/* Returns a list of supported extensions (WARNING: it's pretty big), such as "adx", "dsp", etc.
|
||||
* Mainly for plugins that want to know which extensions are supported.
|
||||
* - returns NULL if no size is provided
|
||||
*/
|
||||
LIBVGMSTREAM_API const char** libvgmstream_get_extensions(size_t* size);
|
||||
|
||||
|
||||
/* Same as above, buf returns a list what vgmstream considers "common" formats (such as "wav", "ogg"),
|
||||
* which usually one doesn't want to associate to vgmstream.
|
||||
* - returns NULL if no size is provided
|
||||
*/
|
||||
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) */
|
||||
} libvgmstream_valid_t;
|
||||
|
||||
/* 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 fail early; libvgmstream doesn't use this
|
||||
*/
|
||||
LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_valid_t* cfg);
|
||||
|
||||
|
||||
typedef struct {
|
||||
bool force_title; // TODO: what was this for?
|
||||
bool subsong_range; // print a range of possible subsongs after title 'filename#1~N'
|
||||
bool remove_extension; // remove extension from passed filename
|
||||
bool remove_archive; // remove '(archive)|(subfile)' format of some plugins
|
||||
const char* filename; // base file's filename
|
||||
// ** note that sometimes vgmstream doesn't have/know the original name, so it's needed again here
|
||||
} libvgmstream_title_t;
|
||||
|
||||
/* get a simple title for plugins, derived from internal stream name if available
|
||||
* - valid after _open
|
||||
*/
|
||||
LIBVGMSTREAM_API int libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_title_t* cfg, char* buf, int buf_len);
|
||||
|
||||
|
||||
/* Writes a description of the current song into dst. Will always be null-terminated.
|
||||
* - returns < 0 if file was truncated, though will still succeed.
|
||||
*/
|
||||
LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_t* lib, char* dst, int dst_size);
|
||||
|
||||
#endif
|
||||
#endif
|
156
src/api_main.c
156
src/api_main.c
@ -1,156 +0,0 @@
|
||||
#if 0
|
||||
#include "api.h"
|
||||
#include "vgmstream.h"
|
||||
|
||||
#define LIBVGMSTREAM_ERROR_GENERIC -1
|
||||
#define LIBVGMSTREAM_OK 0
|
||||
|
||||
|
||||
LIBVGMSTREAM_API uint32_t libvgmstream_get_version(void) {
|
||||
return (LIBVGMSTREAM_API_VERSION_MAJOR << 24) | (LIBVGMSTREAM_API_VERSION_MINOR << 16) | (LIBVGMSTREAM_API_VERSION_PATCH << 0);
|
||||
}
|
||||
|
||||
|
||||
/* vgmstream context/handle */
|
||||
typedef struct {
|
||||
|
||||
libvgmstream_config_t cfg; // current config
|
||||
libvgmstream_format_t fmt; // format config
|
||||
libvgmstream_decoder_t dec; // decoder config
|
||||
} libvgmstream_internal_t;
|
||||
|
||||
|
||||
|
||||
/* base init */
|
||||
LIBVGMSTREAM_API libvgmstream_t* libvgmstream_init(void) {
|
||||
libvgmstream_t* lib = NULL;
|
||||
libvgmstream_internal_t* priv = NULL;
|
||||
|
||||
lib = calloc(1, sizeof(libvgmstream_t));
|
||||
if (!lib) goto fail;
|
||||
|
||||
lib->priv = calloc(1, sizeof(libvgmstream_internal_t));
|
||||
if (!lib->priv) goto fail;
|
||||
|
||||
priv = lib->priv;
|
||||
|
||||
//TODO only setup on decode? (but may less error prone if set)
|
||||
lib->format = &priv->fmt;
|
||||
lib->decoder = &priv->dec;
|
||||
|
||||
return lib;
|
||||
fail:
|
||||
libvgmstream_free(lib);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* base free */
|
||||
LIBVGMSTREAM_API void libvgmstream_free(libvgmstream_t* lib) {
|
||||
if (!lib)
|
||||
return;
|
||||
|
||||
//TODO close stuff in priv
|
||||
//if (lib->priv) {
|
||||
// libvgmstream_internal_t* priv = lib->priv;
|
||||
//}
|
||||
|
||||
free(lib->priv);
|
||||
free(lib);
|
||||
}
|
||||
|
||||
/* current play config */
|
||||
LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_config_t* cfg) {
|
||||
if (!lib)
|
||||
return;
|
||||
if (!cfg)
|
||||
return;
|
||||
libvgmstream_internal_t* priv = lib->priv;
|
||||
|
||||
priv->cfg = *cfg;
|
||||
//TODO
|
||||
}
|
||||
|
||||
/* open new file */
|
||||
LIBVGMSTREAM_API int libvgmstream_open(libvgmstream_t* lib, libvgmstream_options_t* opt) {
|
||||
if (!lib)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
//if (!opt || !opt->sf || opt->subsong < 0)
|
||||
// return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
// close loaded song if any
|
||||
libvgmstream_close(lib);
|
||||
|
||||
//format_internal_id
|
||||
|
||||
// TODO open new vgmstream, save in priv
|
||||
|
||||
return LIBVGMSTREAM_OK;
|
||||
}
|
||||
|
||||
/* query new vgmstream, without closing current one */
|
||||
LIBVGMSTREAM_API int libvgmstream_open_info(libvgmstream_t* lib, libvgmstream_options_t* opt, libvgmstream_format_t* fmt) {
|
||||
//TODO
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
}
|
||||
|
||||
/* close current vgmstream */
|
||||
LIBVGMSTREAM_API void libvgmstream_close(libvgmstream_t* lib) {
|
||||
//TODO close current vgmstream
|
||||
}
|
||||
|
||||
|
||||
/* decodes samples */
|
||||
LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib) {
|
||||
// TODO
|
||||
|
||||
//if (!lib->decoder)
|
||||
// ...
|
||||
|
||||
lib->decoder->done = true;
|
||||
|
||||
return LIBVGMSTREAM_OK;
|
||||
}
|
||||
|
||||
/* vgmstream current decode position */
|
||||
LIBVGMSTREAM_API int64_t libvgmstream_play_position(libvgmstream_t* lib) {
|
||||
// TODO
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* vgmstream seek */
|
||||
LIBVGMSTREAM_API int libvgmstream_seek(libvgmstream_t* lib, int64_t sample) {
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_format_t* format, char* dst, int dst_size) {
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
}
|
||||
|
||||
LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_valid_t* cfg) {
|
||||
//TODO
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API void libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_title_t* cfg, char* buf, int buf_len) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API void libvgmstream_set_log(libvgmstream_log_t* log) {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_get_file(const char* filename) {
|
||||
STREAMFILE* sf = open_stdio_streamfile(filename);
|
||||
//TODO make STREAMFILE compatible with libvgmstream_streamfile_t
|
||||
// would need bridge functions (since defs aren't compatible), not ideal since we are calling functions over functions
|
||||
// but basically not noticeable performance-wise
|
||||
}
|
||||
*/
|
||||
|
||||
#endif
|
235
src/api_main.h
235
src/api_main.h
@ -1,235 +0,0 @@
|
||||
#if 0
|
||||
#ifndef _API_MAIN_H_
|
||||
#define _API_MAIN_H_
|
||||
#include "api.h"
|
||||
#include "api_streamfile.h"
|
||||
|
||||
/* vgmstream's main (decode) API.
|
||||
*/
|
||||
|
||||
/* returns API version in hex format: 0xMMmmpppp = MM-major, mm-minor, pppp-patch
|
||||
* - use when loading vgmstream as a dynamic library to ensure API/ABI compatibility */
|
||||
LIBVGMSTREAM_API uint32_t libvgmstream_get_version(void);
|
||||
|
||||
// interleaved: buf[0]=ch0, buf[1]=ch1, buf[2]=ch0, buf[3]=ch0, ...
|
||||
enum {
|
||||
LIBVGMSTREAM_SAMPLE_PCM16 = 0x01,
|
||||
LIBVGMSTREAM_SAMPLE_FLOAT = 0x02,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
/* main (always set) */
|
||||
const int channels; // output channels
|
||||
const int sample_rate; // output sample rate
|
||||
const int sample_type; // size of resulting samples
|
||||
|
||||
/* extra info (may be 0 if not known or not relevant) */
|
||||
const uint32_t channel_layout; // standard bitflags
|
||||
const int input_channels; // original file's channels before downmixing (if any)
|
||||
|
||||
const int interleave; // when file is interleaved
|
||||
const int frame_size; // when file has some configurable frame size
|
||||
|
||||
const int subsong_index; // 0 = none, N = loaded subsong N
|
||||
const int subsong_count; // 0 = format has no subsongs, N = has N subsongs (1 = format has subsongs and only 1)
|
||||
|
||||
/* sample info (may not be used depending on config) */
|
||||
const int64_t sample_count; // file's max samples (not final play duration)
|
||||
const int64_t loop_start; // loop start sample
|
||||
const int64_t loop_end; // loop end sample
|
||||
const bool loop_flag; // if file loops; note that false + defined loops means looping was forcefully disabled
|
||||
|
||||
const int64_t play_time; // samples after all calculations (after applying loop/fade/etc config)
|
||||
// ** may not be 100% accurate in some cases (must check decoder's 'done' flag)
|
||||
// ** if loop_forever is set this value is provided for reference based on non-forever config
|
||||
|
||||
const bool rough_samples; // signal cases where loop points or sample count can't exactly reflect actual behavior (do not use to export)
|
||||
|
||||
const int stream_bitrate; // average bitrate of the subsong (slightly bloated vs codec_bitrate: incorrect in rare cases)
|
||||
//const int codec_bitrate; // average bitrate of the codec data (not possible/slow to calculate in most cases)
|
||||
|
||||
const int format_internal_id; // may be used when reopening subfiles or similar formats without checking other possible formats first
|
||||
// ** this value WILL change without warning between vgmstream versions/commits, do not store
|
||||
|
||||
/* descriptions */
|
||||
const char codec[256];
|
||||
const char layout[256];
|
||||
const char metadata[256];
|
||||
|
||||
} libvgmstream_format_t;
|
||||
|
||||
|
||||
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, meaning current _decode can't generate samples)
|
||||
int buf_bytes; // current buffer bytes (channels * sample-size * samples)
|
||||
|
||||
bool done; // flag when stream is done playing based on config; will still allow _play calls returning blank samples
|
||||
// ** note that with play_forever this flag is never set
|
||||
} libvgmstream_decoder_t;
|
||||
|
||||
|
||||
/* vgmstream context/handle */
|
||||
typedef struct {
|
||||
void* priv; // internal data
|
||||
|
||||
/* pointers for easier ABI compatibility */
|
||||
libvgmstream_format_t* format; // current song info, updated on _open
|
||||
libvgmstream_decoder_t* decoder; // updated on each _decode call
|
||||
|
||||
} libvgmstream_t;
|
||||
|
||||
|
||||
|
||||
/* inits the vgmstream context
|
||||
* - returns NULL on error
|
||||
* - call libvgmstream_free when done.
|
||||
*/
|
||||
LIBVGMSTREAM_API libvgmstream_t* libvgmstream_init(void);
|
||||
|
||||
/* frees vgmstream context and any other internal stuff that may not be closed
|
||||
*/
|
||||
LIBVGMSTREAM_API void libvgmstream_free(libvgmstream_t* lib);
|
||||
|
||||
|
||||
/* configures how vgmstream behaves internally when playing a file */
|
||||
typedef struct {
|
||||
bool allow_play_forever; // must allow manually as some cases a TXTP may set loop forever but client may not handle it (ex. wave dumpers)
|
||||
|
||||
bool play_forever; // keeps looping forever (file must have loop_flag set)
|
||||
bool ignore_loop; // ignores loops points
|
||||
bool force_loop; // enables full loops (0..samples) if file doesn't have loop points
|
||||
bool really_force_loop; // forces full loops (0..samples) even if file has loop points
|
||||
bool ignore_fade; // don't fade after N loops and play remaning stream (for files with outros)
|
||||
|
||||
double loop_count; // target loops (values like 1.5 are ok)
|
||||
double fade_delay; // fade delay after target loops
|
||||
double fade_time; // fade period after target loops
|
||||
|
||||
int max_channels; // automatic downmixing if vgmstream's channels are higher than max_channels
|
||||
// ** for players that can only handle N channels, but this type of downmixing is very simplistic and not recommended
|
||||
|
||||
int force_format; // forces output buffer to be remixed into some LIBVGMSTREAM_SAMPLE_x format
|
||||
|
||||
//bool disable_config_override; // ignore forced (TXTP) config
|
||||
|
||||
} libvgmstream_config_t;
|
||||
|
||||
/* pass default config, that will be applied to song on open
|
||||
* - invalid config or complex cases (ex. some TXTP) may ignore these settings.
|
||||
* - called without a song loaded (before _open or after _close), otherwise ignored.
|
||||
* - without config vgmstream will decode the current stream once
|
||||
*/
|
||||
LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_config_t* cfg);
|
||||
|
||||
|
||||
/* configures how vgmstream opens the format */
|
||||
typedef struct {
|
||||
libvgmstream_streamfile_t* sf; // custom IO streamfile that provides reader info for vgmstream
|
||||
// ** not needed after _open and should be closed, as vgmstream re-opens its own SFs internally as needed
|
||||
|
||||
int subsong; // target subsong (1..N) or 0 = unknown/first
|
||||
// ** to check if a file has subsongs, _open first + check total_subsongs (then _open 2nd, 3rd, etc)
|
||||
|
||||
void* external_buf; // set a custom-sized sample buf instead of the default provided buf
|
||||
// ** must be at least as big as channels * sample-size * buf_samples
|
||||
// ** libvgmstream decoder->buf will set this buf
|
||||
// ** slower than using the provided buf (needs copying around), mainly if you have fixed sample constraints
|
||||
int external_buf_samples; // max samples the custom buffer may hold
|
||||
|
||||
int format_internal_id; // force a format (for example when loading new subsong)
|
||||
|
||||
int stereo_track; // forces vgmstream to decode one 2ch+2ch+2ch... 'track' and discard other channels, where 0 = disabled, 1..N = Nth track
|
||||
|
||||
} libvgmstream_options_t;
|
||||
|
||||
/* opens file based on config and prepares it to play if supported.
|
||||
* - returns < 0 on error (file not recognised, invalid subsong index, etc)
|
||||
* - will close currently loaded song if needed
|
||||
*/
|
||||
LIBVGMSTREAM_API int libvgmstream_open(libvgmstream_t* lib, libvgmstream_options_t* open_options);
|
||||
|
||||
/* opens file based on config and returns its file info
|
||||
* - returns < 0 on error (file not recognised, invalid subsong index, etc)
|
||||
* - equivalent to _open, but doesn't update the current loaded song / format and copies to passed struct
|
||||
* - may be used while current song is loaded/playing but you need to query next song with current config
|
||||
* - to play a new song don't call _open_info to check the format first, just call _open + check format afterwards
|
||||
* - the only way for vgmstream to know a file's metadata is getting it almost ready to play,
|
||||
* so this isn't any faster than _open
|
||||
*/
|
||||
LIBVGMSTREAM_API int libvgmstream_open_info(libvgmstream_t* lib, libvgmstream_options_t* open_options, libvgmstream_format_t* format);
|
||||
|
||||
/* closes current song; may still use libvgmstream to open other songs
|
||||
*/
|
||||
LIBVGMSTREAM_API void libvgmstream_close(libvgmstream_t* lib);
|
||||
|
||||
|
||||
/* decodes next batch of samples (lib->decoder->* will be updated)
|
||||
* - returns < 0 on error
|
||||
*/
|
||||
LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib);
|
||||
|
||||
/* Gets current position within the song.
|
||||
* - return < 0 on error (file not ready) */
|
||||
LIBVGMSTREAM_API int64_t libvgmstream_play_position(libvgmstream_t* lib);
|
||||
|
||||
/* Seeks to absolute position. Will clamp incorrect values.
|
||||
* - return < 0 on error (ignores seek attempt)
|
||||
* - on play_forever one may locally seek to any position, but there is an internal limit */
|
||||
LIBVGMSTREAM_API int libvgmstream_seek(libvgmstream_t* lib, int64_t sample);
|
||||
|
||||
|
||||
/* Writes a description of the format into dst. Will always be null-terminated.
|
||||
* - returns < 0 if file was truncated, though will still succeed. */
|
||||
LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_format_t* format, char* dst, int dst_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) */
|
||||
} libvgmstream_valid_t;
|
||||
|
||||
/* 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 fail early; libvgmstream doesn't use this
|
||||
*/
|
||||
LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_valid_t* cfg);
|
||||
|
||||
|
||||
//TODO are all these options necessary?
|
||||
typedef struct {
|
||||
bool force_title;
|
||||
bool subsong_range;
|
||||
bool remove_extension;
|
||||
bool remove_archive;
|
||||
const char* filename;
|
||||
} libvgmstream_title_t;
|
||||
|
||||
/* get a simple title for plugins, derived from internal stream name if available
|
||||
* - valid after _open */
|
||||
LIBVGMSTREAM_API void libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_title_t* cfg, char* buf, int buf_len);
|
||||
|
||||
|
||||
enum {
|
||||
LIBVGMSTREAM_LOG_LEVEL_ALL = 0,
|
||||
LIBVGMSTREAM_LOG_LEVEL_DEBUG = 20,
|
||||
LIBVGMSTREAM_LOG_LEVEL_INFO = 30,
|
||||
LIBVGMSTREAM_LOG_LEVEL_NONE = 100,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int level; // log level
|
||||
void (*callback)(int level, const char* str); // log callback
|
||||
bool stdout_callback; // use log callback
|
||||
} libvgmstream_log_t;
|
||||
|
||||
/* defines a global log callback, as vgmstream sometimes communicates format issues to the user.
|
||||
* - note that log is currently set globally rather than per libvgmstream_t
|
||||
*/
|
||||
LIBVGMSTREAM_API void libvgmstream_set_log(libvgmstream_log_t* log);
|
||||
|
||||
#endif
|
||||
#endif
|
@ -1,51 +1,71 @@
|
||||
#if 0
|
||||
#ifndef _API_STREAMFILE_H_
|
||||
#define _API_STREAMFILE_H_
|
||||
#include "api.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
/* vgmstream's IO API, defined as a "streamfile" (SF).
|
||||
*
|
||||
* Unlike more typical IO, vgmstream has particular needs that roughly assume there is some underlying filesystem (as typical of games):
|
||||
* - reading from arbitrary offsets: header not found in the beginning of a stream, rewinding during looping, etc
|
||||
* - opening other streamfiles: reopening a copy of current SF, formats with split header + data, decryption files, etc
|
||||
* - filename: opening similarly named companion files, heuristics when file's data is not enough, etc
|
||||
* Compared to typical IO, vgmstream has some extra needs that roughly assume there is an underlying filesystem (as usual in games):
|
||||
* - seeking + reading from arbitrary offsets: header not in the beginning of a stream, rewinding back when looping, etc
|
||||
* - opening other streamfiles: reopening a copy of the current SF, formats with split header + data, decryption files, etc
|
||||
* - extracting the filename: opening similarly named companion files, basic extension sanity checks, heuristics for odd cases, etc
|
||||
*
|
||||
* If your use case can't satisfy those constraints, it may still be possible to create a streamfile that just simulates part of it.
|
||||
* For example, loading data into memory, returning a fake filename, and only handling "open" that reopens itself (same filename),
|
||||
* while returning default/incorrect values on non-handled operations. Simpler, non-looped formats probably work fine just being read linearly.
|
||||
* If your IO can't fully satisfy those constraints, it may still be possible to create a streamfile that just simulates part of it.
|
||||
* For example, returning a fake filename, and only handling "open" that reopens itself (same filename), while returning default/incorrect
|
||||
* values for non-handled operations. Simpler formats will probably work just fine.
|
||||
*/
|
||||
|
||||
#include "api.h"
|
||||
|
||||
// TODO: pass opaque instead?
|
||||
enum {
|
||||
LIBVGMSTREAM_STREAMFILE_SEEK_SET = 0,
|
||||
LIBVGMSTREAM_STREAMFILE_SEEK_CUR = 1,
|
||||
LIBVGMSTREAM_STREAMFILE_SEEK_END = 2,
|
||||
//LIBVGMSTREAM_STREAMFILE_SEEK_GET_OFFSET = 3,
|
||||
//LIBVGMSTREAM_STREAMFILE_SEEK_GET_SIZE = 5,
|
||||
};
|
||||
|
||||
typedef struct libvgmstream_streamfile_t {
|
||||
/* user data */
|
||||
void* opaque;
|
||||
//uint32_t flags; // info flags for vgmstream
|
||||
void* user_data; // any internal structure
|
||||
|
||||
/* read 'length' data at 'offset' to 'dst' (implicit seek) */
|
||||
size_t (*read)(struct libvgmstream_streamfile_t* sf, uint8_t* dst, int64_t offset, size_t length);
|
||||
/* read 'length' data at internal offset to 'dst' (implicit seek if needed)
|
||||
* - assumes 0 = failure/EOF
|
||||
*/
|
||||
int (*read)(void* user_data, uint8_t* dst, int dst_size);
|
||||
|
||||
/* get max offset */
|
||||
size_t (*get_size)(struct libvgmstream_streamfile_t* sf); //TODO return int64_t?
|
||||
/* seek to offset
|
||||
* - note that due to how vgmstream works this is a fairly common operation (to be optimized later)
|
||||
*/
|
||||
int64_t (*seek)(void* user_data, int64_t offset, int whence);
|
||||
|
||||
/* copy current filename to name buf */
|
||||
void (*get_name)(struct libvgmstream_streamfile_t* sf, char* name, size_t name_size);
|
||||
/* get max offset
|
||||
*/
|
||||
int64_t (*get_size)(void* user_data);
|
||||
|
||||
/* open another streamfile from filename (which may be some internal path/protocol) */
|
||||
struct libvgmstream_streamfile_t* (*open)(struct libvgmstream_streamfile_t* sf, const char* const filename, size_t buf_size);
|
||||
/* copy current filename to name buf
|
||||
*/
|
||||
void (*get_name)(void* user_data, char* name, int name_size); //TODO return char*?
|
||||
|
||||
/* free current STREAMFILE */
|
||||
void (*close)(struct libvgmstream_streamfile_t* sf);
|
||||
/* open another streamfile from filename (may be some path/protocol, or same as current get_name = reopen)
|
||||
* - vgmstream mainly opens stuff based on current get_name (relative), so there shouldn't be need to transform this path
|
||||
*/
|
||||
struct libvgmstream_streamfile_t* (*open)(void* user_data, const char* filename);
|
||||
|
||||
/* free current SF (needed for copied streamfiles) */
|
||||
void (*close)(struct libvgmstream_streamfile_t* libsf);
|
||||
|
||||
} libvgmstream_streamfile_t;
|
||||
|
||||
|
||||
/* helper */
|
||||
static inline void libvgmstream_streamfile_close(libvgmstream_streamfile_t* sf) {
|
||||
if (!sf)
|
||||
static inline void libvgmstream_streamfile_close(libvgmstream_streamfile_t* libsf) {
|
||||
if (!libsf || !libsf->close)
|
||||
return;
|
||||
sf->close(sf);
|
||||
libsf->close(libsf);
|
||||
}
|
||||
|
||||
//LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_get_file(const char* filename);
|
||||
|
||||
LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_from_filename(const char* filename);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -1,37 +1,42 @@
|
||||
#if 0
|
||||
#ifndef _API_TAGS_H_
|
||||
#define _API_TAGS_H_
|
||||
#include "api.h"
|
||||
#include "api_streamfile.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
/* vgmstream's !tags.m3u API.
|
||||
* Doesn't need a main libvgmstream lib as tags aren't tied to loaded songs.
|
||||
* Doesn't need a main libvgmstream as tags aren't tied to loaded songs.
|
||||
*
|
||||
* Meant to be a simple implementation; feel free to ignore and roll your own
|
||||
* (or use another external tags plugin).
|
||||
*/
|
||||
|
||||
|
||||
/* tag state */
|
||||
typedef struct {
|
||||
void* priv; // internal data
|
||||
|
||||
const char* key; // current key
|
||||
const char* val; // current value
|
||||
|
||||
} libvgmstream_tags_t;
|
||||
|
||||
/* Initializes tags.
|
||||
* - may be reused for different files for cache purposed
|
||||
*/
|
||||
LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init();
|
||||
|
||||
/* Restarts tagfile for a new filename. Must be called first before extracting tags.
|
||||
* - libsf should point to a !tags.m3u file
|
||||
* - unlike libvgmstream_open, sf tagfile must be valid during the tag extraction process.
|
||||
*/
|
||||
LIBVGMSTREAM_API void libvgmstream_tags_reset(libvgmstream_tags_t* tags, libvgmstream_streamfile_t* sf, const char* target_filename);
|
||||
LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libvgmstream_streamfile_t* libsf);
|
||||
|
||||
|
||||
/* Finds tags for a new filename. Must be called first before extracting tags.
|
||||
*/
|
||||
LIBVGMSTREAM_API void libvgmstream_tags_find(libvgmstream_tags_t* tags, const char* target_filename);
|
||||
|
||||
|
||||
/* Extracts next valid tag in tagfile to key/val.
|
||||
* - returns 0 if no more tags are found (meant to be called repeatedly until 0)
|
||||
* - returns false if no more tags are found (meant to be called repeatedly until false)
|
||||
* - key/values are trimmed of beginning/end whitespaces and values are in UTF-8
|
||||
*/
|
||||
LIBVGMSTREAM_API int libvgmstream_tags_next_tag(libvgmstream_tags_t* tags);
|
||||
LIBVGMSTREAM_API bool libvgmstream_tags_next_tag(libvgmstream_tags_t* tags);
|
||||
|
||||
|
||||
/* Closes tags. */
|
||||
LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags);
|
||||
|
84
src/base/api_decode_base.c
Normal file
84
src/base/api_decode_base.c
Normal file
@ -0,0 +1,84 @@
|
||||
#include "api_internal.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
#define INTERNAL_BUF_SAMPLES 1024
|
||||
|
||||
|
||||
LIBVGMSTREAM_API uint32_t libvgmstream_get_version(void) {
|
||||
return (LIBVGMSTREAM_API_VERSION_MAJOR << 24) | (LIBVGMSTREAM_API_VERSION_MINOR << 16) | (LIBVGMSTREAM_API_VERSION_PATCH << 0);
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API libvgmstream_t* libvgmstream_init(void) {
|
||||
libvgmstream_t* lib = NULL;
|
||||
libvgmstream_priv_t* priv = NULL;
|
||||
|
||||
lib = calloc(1, sizeof(libvgmstream_t));
|
||||
if (!lib) goto fail;
|
||||
|
||||
lib->priv = calloc(1, sizeof(libvgmstream_priv_t));
|
||||
if (!lib->priv) goto fail;
|
||||
|
||||
priv = lib->priv;
|
||||
|
||||
//TODO only setup on decode? (but may less error prone if always set)
|
||||
lib->format = &priv->fmt;
|
||||
lib->decoder = &priv->dec;
|
||||
|
||||
priv->cfg.loop_count = 1; //TODO: loop 0 means no loop (improve detection)
|
||||
|
||||
return lib;
|
||||
fail:
|
||||
libvgmstream_free(lib);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API void libvgmstream_free(libvgmstream_t* lib) {
|
||||
if (!lib)
|
||||
return;
|
||||
|
||||
libvgmstream_priv_t* priv = lib->priv;
|
||||
if (priv) {
|
||||
close_vgmstream(priv->vgmstream);
|
||||
free(priv->buf.data);
|
||||
}
|
||||
|
||||
free(priv);
|
||||
free(lib);
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_config_t* cfg) {
|
||||
if (!lib || !lib->priv)
|
||||
return;
|
||||
|
||||
libvgmstream_priv_t* priv = lib->priv;
|
||||
if (!cfg) {
|
||||
memset(&priv->cfg , 0, sizeof(libvgmstream_config_t));
|
||||
priv->cfg.loop_count = 1; //TODO: loop 0 means no loop (improve detection)
|
||||
}
|
||||
else {
|
||||
priv->cfg = *cfg;
|
||||
}
|
||||
|
||||
//TODO validate, etc
|
||||
}
|
||||
|
||||
|
||||
void libvgmstream_priv_reset(libvgmstream_priv_t* priv, bool reset_buf) {
|
||||
//memset(&priv->cfg, 0, sizeof(libvgmstream_config_t)); //config is always valid
|
||||
memset(&priv->fmt, 0, sizeof(libvgmstream_format_t));
|
||||
memset(&priv->dec, 0, sizeof(libvgmstream_decoder_t));
|
||||
//memset(&priv->pos, 0, sizeof(libvgmstream_priv_position_t)); //position info is updated on open
|
||||
|
||||
if (reset_buf) {
|
||||
free(priv->buf.data);
|
||||
memset(&priv->buf, 0, sizeof(libvgmstream_priv_buf_t));
|
||||
}
|
||||
|
||||
priv->pos.current = 0;
|
||||
priv->decode_done = false;
|
||||
}
|
||||
|
||||
#endif
|
138
src/base/api_decode_open.c
Normal file
138
src/base/api_decode_open.c
Normal file
@ -0,0 +1,138 @@
|
||||
#include "api_internal.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
#define INTERNAL_BUF_SAMPLES 1024
|
||||
|
||||
|
||||
static void load_vgmstream(libvgmstream_priv_t* priv, libvgmstream_options_t* opt) {
|
||||
STREAMFILE* sf_api = open_api_streamfile(opt->libsf);
|
||||
if (!sf_api)
|
||||
return;
|
||||
|
||||
//TODO: handle format_internal_id
|
||||
|
||||
sf_api->stream_index = opt->subsong;
|
||||
priv->vgmstream = init_vgmstream_from_STREAMFILE(sf_api);
|
||||
close_streamfile(sf_api);
|
||||
}
|
||||
|
||||
static void apply_config(libvgmstream_priv_t* priv) {
|
||||
libvgmstream_config_t* cfg = &priv->cfg;
|
||||
|
||||
vgmstream_cfg_t vcfg = {
|
||||
.disable_config_override = cfg->disable_config_override,
|
||||
.allow_play_forever = cfg->allow_play_forever,
|
||||
|
||||
.play_forever = cfg->play_forever,
|
||||
.ignore_loop = cfg->ignore_loop,
|
||||
.force_loop = cfg->force_loop,
|
||||
.really_force_loop = cfg->really_force_loop,
|
||||
.ignore_fade = cfg->ignore_fade,
|
||||
|
||||
.loop_count = cfg->loop_count,
|
||||
.fade_time = cfg->fade_time,
|
||||
.fade_delay = cfg->fade_delay,
|
||||
};
|
||||
|
||||
if (!vcfg.allow_play_forever)
|
||||
vcfg.play_forever = 0;
|
||||
|
||||
vgmstream_apply_config(priv->vgmstream, &vcfg);
|
||||
}
|
||||
|
||||
static void prepare_mixing(libvgmstream_priv_t* priv, libvgmstream_options_t* opt) {
|
||||
/* enable after config but before outbuf */
|
||||
if (priv->cfg.auto_downmix_channels) {
|
||||
vgmstream_mixing_autodownmix(priv->vgmstream, priv->cfg.auto_downmix_channels);
|
||||
}
|
||||
else if (opt && opt->stereo_track >= 1) {
|
||||
vgmstream_mixing_stereo_only(priv->vgmstream, opt->stereo_track - 1);
|
||||
}
|
||||
|
||||
vgmstream_mixing_enable(priv->vgmstream, INTERNAL_BUF_SAMPLES, NULL /*&input_channels*/, NULL /*&output_channels*/);
|
||||
}
|
||||
|
||||
static void update_position(libvgmstream_priv_t* priv) {
|
||||
libvgmstream_priv_position_t* pos = &priv->pos;
|
||||
VGMSTREAM* v = priv->vgmstream;
|
||||
|
||||
pos->play_forever = vgmstream_get_play_forever(v);
|
||||
pos->play_samples = vgmstream_get_samples(v);
|
||||
pos->current = 0;
|
||||
}
|
||||
|
||||
static void update_format_info(libvgmstream_priv_t* priv) {
|
||||
libvgmstream_format_t* fmt = &priv->fmt;
|
||||
VGMSTREAM* v = priv->vgmstream;
|
||||
|
||||
fmt->sample_size = 0x02;
|
||||
fmt->sample_type = LIBVGMSTREAM_SAMPLE_PCM16;
|
||||
fmt->sample_rate = v->sample_rate;
|
||||
|
||||
fmt->subsong_index = v->stream_index;
|
||||
fmt->subsong_count = v->num_streams;
|
||||
|
||||
fmt->channels = v->channels;
|
||||
fmt->input_channels = 0;
|
||||
vgmstream_mixing_enable(v, 0, &fmt->input_channels, &fmt->channels);
|
||||
fmt->channel_layout = v->channel_layout;
|
||||
|
||||
fmt->sample_count = v->num_samples;
|
||||
fmt->loop_start = v->loop_start_sample;
|
||||
fmt->loop_end = v->loop_end_sample;
|
||||
fmt->loop_flag = v->loop_flag;
|
||||
|
||||
fmt->play_forever = priv->pos.play_forever;
|
||||
fmt->play_samples = priv->pos.play_samples;
|
||||
|
||||
fmt->stream_bitrate = get_vgmstream_average_bitrate(v);
|
||||
|
||||
get_vgmstream_coding_description(v, fmt->codec_name, sizeof(fmt->codec_name));
|
||||
get_vgmstream_layout_description(v, fmt->layout_name, sizeof(fmt->layout_name));
|
||||
get_vgmstream_meta_description(v, fmt->meta_name, sizeof(fmt->meta_name));
|
||||
|
||||
if (v->stream_name[0] != '\0') { //snprintf UB for NULL args
|
||||
snprintf(fmt->stream_name, sizeof(fmt->stream_name), "%s", v->stream_name);
|
||||
}
|
||||
|
||||
fmt->format_internal_id = 0; //TODO
|
||||
}
|
||||
|
||||
LIBVGMSTREAM_API int libvgmstream_open(libvgmstream_t* lib, libvgmstream_options_t* opt) {
|
||||
if (!lib ||!lib->priv)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
if (!opt || !opt->libsf || opt->subsong < 0)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
// close loaded song if any + reset
|
||||
libvgmstream_close(lib);
|
||||
|
||||
libvgmstream_priv_t* priv = lib->priv;
|
||||
|
||||
load_vgmstream(priv, opt);
|
||||
if (!priv->vgmstream)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
apply_config(priv);
|
||||
prepare_mixing(priv, opt);
|
||||
update_position(priv);
|
||||
|
||||
update_format_info(priv);
|
||||
|
||||
|
||||
return LIBVGMSTREAM_OK;
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API void libvgmstream_close(libvgmstream_t* lib) {
|
||||
if (!lib || !lib->priv)
|
||||
return;
|
||||
|
||||
libvgmstream_priv_t* priv = lib->priv;
|
||||
|
||||
close_vgmstream(priv->vgmstream);
|
||||
priv->vgmstream = NULL;
|
||||
|
||||
libvgmstream_priv_reset(priv, true);
|
||||
}
|
||||
|
||||
#endif
|
142
src/base/api_decode_play.c
Normal file
142
src/base/api_decode_play.c
Normal file
@ -0,0 +1,142 @@
|
||||
#include "api_internal.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
#define INTERNAL_BUF_SAMPLES 1024
|
||||
//TODO: use internal
|
||||
|
||||
static void reset_buf(libvgmstream_priv_t* priv) {
|
||||
/* state reset */
|
||||
priv->buf.samples = 0;
|
||||
priv->buf.bytes = 0;
|
||||
priv->buf.consumed = 0;
|
||||
|
||||
if (priv->buf.initialized)
|
||||
return;
|
||||
int output_channels = priv->vgmstream->channels;
|
||||
int input_channels = priv->vgmstream->channels;
|
||||
vgmstream_mixing_enable(priv->vgmstream, 0, &input_channels, &output_channels); //query
|
||||
|
||||
/* static config */
|
||||
priv->buf.channels = input_channels;
|
||||
if (priv->buf.channels < output_channels)
|
||||
priv->buf.channels = output_channels;
|
||||
|
||||
priv->buf.sample_size = sizeof(sample_t);
|
||||
priv->buf.max_samples = INTERNAL_BUF_SAMPLES;
|
||||
priv->buf.max_bytes = priv->buf.max_samples * priv->buf.sample_size * priv->buf.channels;
|
||||
priv->buf.data = malloc(priv->buf.max_bytes);
|
||||
|
||||
priv->buf.initialized = true;
|
||||
}
|
||||
|
||||
static void update_buf(libvgmstream_priv_t* priv, int samples_done) {
|
||||
priv->buf.samples = samples_done;
|
||||
priv->buf.bytes = samples_done * priv->buf.sample_size * priv->buf.channels;
|
||||
//priv->buf.consumed = 0; //external
|
||||
|
||||
if (!priv->pos.play_forever) {
|
||||
priv->decode_done = (priv->pos.current >= priv->pos.play_samples);
|
||||
priv->pos.current += samples_done;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// update decoder info based on last render, though at the moment it's all fixed
|
||||
static void update_decoder_info(libvgmstream_priv_t* priv, int samples_done) {
|
||||
|
||||
// output copy
|
||||
priv->dec.buf = priv->buf.data;
|
||||
priv->dec.buf_bytes = priv->buf.bytes;
|
||||
priv->dec.buf_samples = priv->buf.samples;
|
||||
priv->dec.done = priv->decode_done;
|
||||
}
|
||||
|
||||
LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib) {
|
||||
if (!lib || !lib->priv)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
libvgmstream_priv_t* priv = lib->priv;
|
||||
if (priv->decode_done)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
reset_buf(priv);
|
||||
if (!priv->buf.data)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
int to_get = priv->buf.max_samples;
|
||||
if (!priv->pos.play_forever && to_get + priv->pos.current > priv->pos.play_samples)
|
||||
to_get = priv->pos.play_samples - priv->pos.current;
|
||||
|
||||
int decoded = render_vgmstream(priv->buf.data, to_get, priv->vgmstream);
|
||||
update_buf(priv, decoded);
|
||||
update_decoder_info(priv, decoded);
|
||||
|
||||
return LIBVGMSTREAM_OK;
|
||||
}
|
||||
|
||||
|
||||
/* _play decodes a single frame, while this copies partially that frame until frame is over */
|
||||
LIBVGMSTREAM_API int libvgmstream_fill(libvgmstream_t* lib, void* buf, int buf_samples) {
|
||||
if (!lib || !lib->priv || !buf || !buf_samples)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
libvgmstream_priv_t* priv = lib->priv;
|
||||
if (priv->decode_done)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
if (priv->buf.consumed >= priv->buf.samples) {
|
||||
int err = libvgmstream_play(lib);
|
||||
if (err < 0) return err;
|
||||
}
|
||||
|
||||
int copy_samples = priv->buf.samples;
|
||||
if (copy_samples > buf_samples)
|
||||
copy_samples = buf_samples;
|
||||
int copy_bytes = priv->buf.sample_size * priv->buf.channels * copy_samples;
|
||||
int skip_bytes = priv->buf.sample_size * priv->buf.channels * priv->buf.consumed;
|
||||
|
||||
memcpy(buf, ((uint8_t*)priv->buf.data) + skip_bytes, copy_bytes);
|
||||
priv->buf.consumed += copy_samples;
|
||||
|
||||
return copy_samples;
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API int64_t libvgmstream_get_play_position(libvgmstream_t* lib) {
|
||||
if (!lib || !lib->priv)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
libvgmstream_priv_t* priv = lib->priv;
|
||||
if (!priv->vgmstream)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
return priv->vgmstream->pstate.play_position;
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API void libvgmstream_seek(libvgmstream_t* lib, int64_t sample) {
|
||||
if (!lib || !lib->priv)
|
||||
return;
|
||||
|
||||
libvgmstream_priv_t* priv = lib->priv;
|
||||
if (!priv->vgmstream)
|
||||
return;
|
||||
|
||||
seek_vgmstream(priv->vgmstream, sample);
|
||||
|
||||
priv->pos.current = priv->vgmstream->pstate.play_position;
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API void libvgmstream_reset(libvgmstream_t* lib) {
|
||||
if (!lib || !lib->priv)
|
||||
return;
|
||||
|
||||
libvgmstream_priv_t* priv = lib->priv;
|
||||
if (priv->vgmstream) {
|
||||
reset_vgmstream(priv->vgmstream);
|
||||
}
|
||||
libvgmstream_priv_reset(priv, false);
|
||||
}
|
||||
|
||||
#endif
|
91
src/base/api_helpers.c
Normal file
91
src/base/api_helpers.c
Normal file
@ -0,0 +1,91 @@
|
||||
#include "api_internal.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
|
||||
static int get_internal_log_level(libvgmstream_loglevel_t level) {
|
||||
switch(level) {
|
||||
case LIBVGMSTREAM_LOG_LEVEL_ALL: return VGM_LOG_LEVEL_ALL;
|
||||
case LIBVGMSTREAM_LOG_LEVEL_DEBUG: return VGM_LOG_LEVEL_DEBUG;
|
||||
case LIBVGMSTREAM_LOG_LEVEL_INFO: return VGM_LOG_LEVEL_INFO;
|
||||
case LIBVGMSTREAM_LOG_LEVEL_NONE:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
LIBVGMSTREAM_API void libvgmstream_set_log(libvgmstream_log_t* cfg) {
|
||||
if (!cfg)
|
||||
return;
|
||||
|
||||
int ilevel = get_internal_log_level(cfg->level);
|
||||
if (cfg->stdout_callback) {
|
||||
//vgmstream_set_log_stdout(ilevel);
|
||||
vgm_log_set_callback(NULL, ilevel, 1, NULL);
|
||||
}
|
||||
else {
|
||||
//vgmstream_set_log_callback(ilevel, cfg->callback);
|
||||
vgm_log_set_callback(NULL, ilevel, 0, cfg->callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API const char** libvgmstream_get_extensions(size_t* size) {
|
||||
return vgmstream_get_formats(size);
|
||||
}
|
||||
|
||||
LIBVGMSTREAM_API const char** libvgmstream_get_common_extensions(size_t* size) {
|
||||
return vgmstream_get_common_formats(size);
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_t* lib, char* dst, int dst_size) {
|
||||
if (!lib || !lib->priv)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
libvgmstream_priv_t* priv = lib->priv;
|
||||
if (!priv->vgmstream)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
describe_vgmstream(priv->vgmstream, dst, dst_size);
|
||||
return LIBVGMSTREAM_OK; //TODO return truncated chars
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_valid_t* cfg) {
|
||||
if (!filename)
|
||||
return false;
|
||||
|
||||
if (!cfg)
|
||||
return vgmstream_ctx_is_valid(filename, NULL);
|
||||
|
||||
vgmstream_ctx_valid_cfg icfg = {
|
||||
.is_extension = cfg->is_extension,
|
||||
.skip_standard = cfg->skip_default,
|
||||
.reject_extensionless = cfg->reject_extensionless,
|
||||
.accept_unknown = cfg->accept_unknown,
|
||||
.accept_common = cfg->accept_common
|
||||
};
|
||||
return vgmstream_ctx_is_valid(filename, &icfg);
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API int libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_title_t* cfg, char* buf, int buf_len) {
|
||||
if (!buf || !buf_len || !cfg)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
buf[0] = '\0';
|
||||
if (!lib || !lib->priv)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
libvgmstream_priv_t* priv = lib->priv;
|
||||
vgmstream_title_t icfg = {
|
||||
.force_title = cfg->force_title,
|
||||
.subsong_range = cfg->subsong_range,
|
||||
.remove_extension = cfg->remove_extension,
|
||||
.remove_archive = cfg->remove_archive,
|
||||
};
|
||||
vgmstream_get_title(buf, buf_len, cfg->filename, priv->vgmstream, &icfg);
|
||||
return LIBVGMSTREAM_OK;
|
||||
}
|
||||
|
||||
#endif
|
62
src/base/api_internal.h
Normal file
62
src/base/api_internal.h
Normal file
@ -0,0 +1,62 @@
|
||||
#ifndef _API_INTERNAL_H_
|
||||
#define _API_INTERNAL_H_
|
||||
#include "../api.h"
|
||||
#include "../vgmstream.h"
|
||||
#include "plugins.h"
|
||||
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
#define LIBVGMSTREAM_OK 0
|
||||
#define LIBVGMSTREAM_ERROR_GENERIC -1
|
||||
#define LIBVGMSTREAM_ERROR_DONE -2
|
||||
|
||||
/* self-note: various API functions are just bridges to internal stuff.
|
||||
* Rather than changing the internal stuff to handle API structs/etc,
|
||||
* leave internals untouched for a while so external plugins/users may adapt.
|
||||
* (all the bridging around may be a tiiiiny bit slower but in this day and age potatos are pretty powerful) */
|
||||
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
void* data;
|
||||
|
||||
/* config */
|
||||
int channels;
|
||||
int max_bytes;
|
||||
int max_samples;
|
||||
int sample_size;
|
||||
|
||||
/* state */
|
||||
int samples;
|
||||
int bytes;
|
||||
int consumed;
|
||||
|
||||
} libvgmstream_priv_buf_t;
|
||||
|
||||
typedef struct {
|
||||
int64_t play_forever;
|
||||
int64_t play_samples;
|
||||
int64_t current;
|
||||
} libvgmstream_priv_position_t;
|
||||
|
||||
/* vgmstream context/handle */
|
||||
typedef struct {
|
||||
libvgmstream_format_t fmt; // externally exposed
|
||||
libvgmstream_decoder_t dec; // externally exposed
|
||||
|
||||
libvgmstream_config_t cfg; // internal copy
|
||||
|
||||
VGMSTREAM* vgmstream;
|
||||
|
||||
libvgmstream_priv_buf_t buf;
|
||||
libvgmstream_priv_position_t pos;
|
||||
|
||||
bool decode_done;
|
||||
} libvgmstream_priv_t;
|
||||
|
||||
|
||||
void libvgmstream_priv_reset(libvgmstream_priv_t* priv, bool reset_buf);
|
||||
|
||||
STREAMFILE* open_api_streamfile(libvgmstream_streamfile_t* libsf);
|
||||
|
||||
#endif
|
||||
#endif
|
147
src/base/api_libsf.c
Normal file
147
src/base/api_libsf.c
Normal file
@ -0,0 +1,147 @@
|
||||
#include "api_internal.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
static libvgmstream_streamfile_t* libvgmstream_streamfile_from_streamfile(STREAMFILE* sf);
|
||||
|
||||
/* libvgmstream_streamfile_t for external use, as a default implementation calling some internal SF */
|
||||
|
||||
typedef struct {
|
||||
int64_t offset;
|
||||
int64_t size;
|
||||
STREAMFILE* inner_sf;
|
||||
} libsf_data_t;
|
||||
|
||||
static int libsf_read(void* user_data, uint8_t* dst, int dst_size) {
|
||||
libsf_data_t* data = user_data;
|
||||
if (!data || !dst)
|
||||
return 0;
|
||||
|
||||
int bytes = data->inner_sf->read(data->inner_sf, dst, data->offset, dst_size);
|
||||
data->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)
|
||||
return -1;
|
||||
|
||||
switch (whence) {
|
||||
case LIBVGMSTREAM_STREAMFILE_SEEK_SET: /* absolute */
|
||||
break;
|
||||
case LIBVGMSTREAM_STREAMFILE_SEEK_CUR: /* relative to current */
|
||||
offset += data->offset;
|
||||
break;
|
||||
case LIBVGMSTREAM_STREAMFILE_SEEK_END: /* relative to file end (should be negative) */
|
||||
offset += data->size;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* clamp offset like fseek */
|
||||
if (offset > data->size)
|
||||
offset = data->size;
|
||||
else if (offset < 0)
|
||||
offset = 0;
|
||||
|
||||
/* main seek */
|
||||
data->offset = offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int64_t libsf_get_size(void* user_data) {
|
||||
libsf_data_t* data = user_data;
|
||||
if (!data)
|
||||
return 0;
|
||||
return data->size;
|
||||
}
|
||||
|
||||
static void libsf_get_name(void* user_data, char* name, int name_size) {
|
||||
if (!name || !name_size)
|
||||
return;
|
||||
name[0] = '\0';
|
||||
|
||||
libsf_data_t* data = user_data;
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
data->inner_sf->get_name(data->inner_sf, name, name_size); /* default */
|
||||
}
|
||||
|
||||
struct libvgmstream_streamfile_t* libsf_open(void* user_data, const char* filename) {
|
||||
libsf_data_t* data = user_data;
|
||||
if (!data || !data->inner_sf)
|
||||
return NULL;
|
||||
|
||||
STREAMFILE* sf = data->inner_sf->open(data->inner_sf, filename, 0);
|
||||
if (!sf)
|
||||
return NULL;
|
||||
|
||||
libvgmstream_streamfile_t* libsf = libvgmstream_streamfile_from_streamfile(sf);
|
||||
if (!libsf) {
|
||||
close_streamfile(sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return libsf;
|
||||
}
|
||||
|
||||
static void libsf_close(struct libvgmstream_streamfile_t* libsf) {
|
||||
if (!libsf)
|
||||
return;
|
||||
|
||||
libsf_data_t* data = libsf->user_data;
|
||||
if (data && data->inner_sf) {
|
||||
data->inner_sf->close(data->inner_sf);
|
||||
}
|
||||
free(data);
|
||||
free(libsf);
|
||||
}
|
||||
|
||||
static libvgmstream_streamfile_t* libvgmstream_streamfile_from_streamfile(STREAMFILE* sf) {
|
||||
if (!sf)
|
||||
return NULL;
|
||||
|
||||
libvgmstream_streamfile_t* libsf = NULL;
|
||||
libsf_data_t* data = NULL;
|
||||
|
||||
libsf = calloc(1, sizeof(libvgmstream_streamfile_t));
|
||||
if (!libsf) goto fail;
|
||||
|
||||
libsf->read = libsf_read;
|
||||
libsf->seek = libsf_seek;
|
||||
libsf->get_size = libsf_get_size;
|
||||
libsf->get_name = libsf_get_name;
|
||||
libsf->open = libsf_open;
|
||||
libsf->close = libsf_close;
|
||||
|
||||
libsf->user_data = calloc(1, sizeof(libsf_data_t));
|
||||
if (!libsf->user_data) goto fail;
|
||||
|
||||
data = libsf->user_data;
|
||||
data->inner_sf = sf;
|
||||
data->size = get_streamfile_size(sf);
|
||||
|
||||
return libsf;
|
||||
fail:
|
||||
libsf_close(libsf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_from_filename(const char* filename) {
|
||||
STREAMFILE* sf = open_stdio_streamfile(filename);
|
||||
if (!sf)
|
||||
return NULL;
|
||||
|
||||
libvgmstream_streamfile_t* libsf = libvgmstream_streamfile_from_streamfile(sf);
|
||||
if (!libsf) {
|
||||
close_streamfile(sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return libsf;
|
||||
}
|
||||
#endif
|
69
src/base/api_tags.c
Normal file
69
src/base/api_tags.c
Normal file
@ -0,0 +1,69 @@
|
||||
#include "api_internal.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
typedef struct {
|
||||
VGMSTREAM_TAGS* vtags;
|
||||
libvgmstream_streamfile_t* libsf;
|
||||
STREAMFILE* sf_tags;
|
||||
} libvgmstream_tags_priv_t;
|
||||
|
||||
LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libvgmstream_streamfile_t* libsf) {
|
||||
if (!libsf)
|
||||
return NULL;
|
||||
|
||||
libvgmstream_tags_t* tags = NULL;
|
||||
libvgmstream_tags_priv_t* priv = NULL;
|
||||
|
||||
tags = calloc(1, sizeof(libvgmstream_tags_t));
|
||||
if (!tags) goto fail;
|
||||
|
||||
tags->priv = calloc(1, sizeof(libvgmstream_tags_priv_t));
|
||||
if (!tags->priv) goto fail;
|
||||
|
||||
priv = tags->priv;
|
||||
priv->vtags = vgmstream_tags_init(&tags->key, &tags->val);
|
||||
if (!priv->vtags) goto fail;
|
||||
|
||||
priv->sf_tags = open_api_streamfile(libsf);
|
||||
if (!priv->sf_tags) goto fail;
|
||||
|
||||
return tags;
|
||||
fail:
|
||||
libvgmstream_tags_free(tags);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API void libvgmstream_tags_find(libvgmstream_tags_t* tags, const char* target_filename) {
|
||||
if (!tags || !tags->priv || !target_filename)
|
||||
return;
|
||||
//TODO: handle NULL filename?
|
||||
libvgmstream_tags_priv_t* priv = tags->priv;
|
||||
vgmstream_tags_reset(priv->vtags, target_filename);
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API bool libvgmstream_tags_next_tag(libvgmstream_tags_t* tags) {
|
||||
if (!tags)
|
||||
return false;
|
||||
|
||||
libvgmstream_tags_priv_t* priv = tags->priv;
|
||||
|
||||
return vgmstream_tags_next_tag(priv->vtags, priv->sf_tags);
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags) {
|
||||
if (!tags)
|
||||
return;
|
||||
|
||||
libvgmstream_tags_priv_t* priv = tags->priv;
|
||||
if (priv) {
|
||||
vgmstream_tags_close(priv->vtags);
|
||||
close_streamfile(priv->sf_tags);
|
||||
}
|
||||
free(tags->priv);
|
||||
free(tags);
|
||||
}
|
||||
|
||||
#endif
|
107
src/base/config.c
Normal file
107
src/base/config.c
Normal file
@ -0,0 +1,107 @@
|
||||
#include "../vgmstream.h"
|
||||
#include "../util/log.h"
|
||||
#include "plugins.h"
|
||||
#include "mixing.h"
|
||||
|
||||
|
||||
|
||||
static void copy_time(int* dst_flag, int32_t* dst_time, double* dst_time_s, int* src_flag, int32_t* src_time, double* src_time_s) {
|
||||
if (!*src_flag)
|
||||
return;
|
||||
*dst_flag = 1;
|
||||
*dst_time = *src_time;
|
||||
*dst_time_s = *src_time_s;
|
||||
}
|
||||
|
||||
//todo reuse in txtp?
|
||||
static void load_default_config(play_config_t* def, play_config_t* tcfg) {
|
||||
|
||||
/* loop limit: txtp #L > txtp #l > player #L > player #l */
|
||||
if (tcfg->play_forever) {
|
||||
def->play_forever = 1;
|
||||
def->ignore_loop = 0;
|
||||
}
|
||||
if (tcfg->loop_count_set) {
|
||||
def->loop_count = tcfg->loop_count;
|
||||
def->loop_count_set = 1;
|
||||
def->ignore_loop = 0;
|
||||
if (!tcfg->play_forever)
|
||||
def->play_forever = 0;
|
||||
}
|
||||
|
||||
/* fade priority: #F > #f, #d */
|
||||
if (tcfg->ignore_fade) {
|
||||
def->ignore_fade = 1;
|
||||
}
|
||||
if (tcfg->fade_delay_set) {
|
||||
def->fade_delay = tcfg->fade_delay;
|
||||
def->fade_delay_set = 1;
|
||||
}
|
||||
if (tcfg->fade_time_set) {
|
||||
def->fade_time = tcfg->fade_time;
|
||||
def->fade_time_set = 1;
|
||||
}
|
||||
|
||||
/* loop priority: #i > #e > #E (respect player's ignore too) */
|
||||
if (tcfg->really_force_loop) {
|
||||
//def->ignore_loop = 0;
|
||||
def->force_loop = 0;
|
||||
def->really_force_loop = 1;
|
||||
}
|
||||
if (tcfg->force_loop) {
|
||||
//def->ignore_loop = 0;
|
||||
def->force_loop = 1;
|
||||
def->really_force_loop = 0;
|
||||
}
|
||||
if (tcfg->ignore_loop) {
|
||||
def->ignore_loop = 1;
|
||||
def->force_loop = 0;
|
||||
def->really_force_loop = 0;
|
||||
}
|
||||
|
||||
copy_time(&def->pad_begin_set, &def->pad_begin, &def->pad_begin_s, &tcfg->pad_begin_set, &tcfg->pad_begin, &tcfg->pad_begin_s);
|
||||
copy_time(&def->pad_end_set, &def->pad_end, &def->pad_end_s, &tcfg->pad_end_set, &tcfg->pad_end, &tcfg->pad_end_s);
|
||||
copy_time(&def->trim_begin_set, &def->trim_begin, &def->trim_begin_s, &tcfg->trim_begin_set, &tcfg->trim_begin, &tcfg->trim_begin_s);
|
||||
copy_time(&def->trim_end_set, &def->trim_end, &def->trim_end_s, &tcfg->trim_end_set, &tcfg->trim_end, &tcfg->trim_end_s);
|
||||
copy_time(&def->body_time_set, &def->body_time, &def->body_time_s, &tcfg->body_time_set, &tcfg->body_time, &tcfg->body_time_s);
|
||||
|
||||
def->is_mini_txtp = tcfg->is_mini_txtp;
|
||||
def->is_txtp = tcfg->is_txtp;
|
||||
}
|
||||
|
||||
static void load_player_config(play_config_t* def, vgmstream_cfg_t* vcfg) {
|
||||
def->play_forever = vcfg->play_forever;
|
||||
def->ignore_loop = vcfg->ignore_loop;
|
||||
def->force_loop = vcfg->force_loop;
|
||||
def->really_force_loop = vcfg->really_force_loop;
|
||||
def->ignore_fade = vcfg->ignore_fade;
|
||||
|
||||
def->loop_count = vcfg->loop_count;
|
||||
def->loop_count_set = 1;
|
||||
def->fade_delay = vcfg->fade_delay;
|
||||
def->fade_delay_set = 1;
|
||||
def->fade_time = vcfg->fade_time;
|
||||
def->fade_time_set = 1;
|
||||
}
|
||||
|
||||
void vgmstream_apply_config(VGMSTREAM* vgmstream, vgmstream_cfg_t* vcfg) {
|
||||
play_config_t defs = {0};
|
||||
play_config_t* def = &defs; /* for convenience... */
|
||||
play_config_t* tcfg = &vgmstream->config;
|
||||
|
||||
|
||||
load_player_config(def, vcfg);
|
||||
def->config_set = 1;
|
||||
|
||||
if (!vcfg->disable_config_override)
|
||||
load_default_config(def, tcfg);
|
||||
|
||||
if (!vcfg->allow_play_forever)
|
||||
def->play_forever = 0;
|
||||
|
||||
/* copy final config back */
|
||||
*tcfg = *def;
|
||||
|
||||
vgmstream->config_enabled = def->config_set;
|
||||
setup_state_vgmstream(vgmstream);
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
#include "../vgmstream.h"
|
||||
#include "../util/log.h"
|
||||
#include "../util/reader_sf.h"
|
||||
#include "../util/reader_text.h"
|
||||
#include "plugins.h"
|
||||
#include "mixing.h"
|
||||
|
||||
@ -16,8 +14,13 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
|
||||
const char* extension;
|
||||
int i;
|
||||
|
||||
bool is_extension = cfg && cfg->is_extension;
|
||||
bool reject_extensionless = cfg && cfg->reject_extensionless;
|
||||
bool skip_standard = cfg && cfg->skip_standard;
|
||||
bool accept_common = cfg && cfg->accept_common;
|
||||
bool accept_unknown = cfg && cfg->accept_common;
|
||||
|
||||
if (cfg->is_extension) {
|
||||
if (is_extension) {
|
||||
extension = filename;
|
||||
} else {
|
||||
extension = filename_extension(filename);
|
||||
@ -26,15 +29,15 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
|
||||
/* some metas accept extensionless files, but make sure it's not a path (unlikely but...) */
|
||||
if (strlen(extension) <= 0) {
|
||||
int len = strlen(filename); /* foobar passes an extension as so len may be still 0 */
|
||||
if (len <= 0 && !cfg->is_extension)
|
||||
if (len <= 0 && is_extension)
|
||||
return 0;
|
||||
if (len > 1 && (filename[len - 1] == '/' || filename[len - 1] == '\\'))
|
||||
return 0;
|
||||
return !cfg->reject_extensionless;
|
||||
return !reject_extensionless;
|
||||
}
|
||||
|
||||
/* try in default list */
|
||||
if (!cfg->skip_standard) {
|
||||
if (!skip_standard) {
|
||||
extension_list = vgmstream_get_formats(&extension_list_len);
|
||||
for (i = 0; i < extension_list_len; i++) {
|
||||
if (strcasecmp(extension, extension_list[i]) == 0) {
|
||||
@ -44,7 +47,7 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
|
||||
}
|
||||
|
||||
/* try in common extensions */
|
||||
if (cfg->accept_common) {
|
||||
if (accept_common) {
|
||||
extension_list = vgmstream_get_common_formats(&extension_list_len);
|
||||
for (i = 0; i < extension_list_len; i++) {
|
||||
if (strcasecmp(extension, extension_list[i]) == 0)
|
||||
@ -53,7 +56,7 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) {
|
||||
}
|
||||
|
||||
/* allow anything not in the normal list but not in common extensions */
|
||||
if (cfg->accept_unknown) {
|
||||
if (accept_unknown) {
|
||||
int is_common = 0;
|
||||
|
||||
extension_list = vgmstream_get_common_formats(&extension_list_len);
|
||||
@ -76,7 +79,12 @@ void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM
|
||||
char* pos2;
|
||||
char temp[1024];
|
||||
|
||||
if (!buf || !buf_len)
|
||||
return;
|
||||
|
||||
buf[0] = '\0';
|
||||
if (!vgmstream || !filename)
|
||||
return;
|
||||
|
||||
/* name without path */
|
||||
pos = strrchr(filename, '\\');
|
||||
@ -144,380 +152,6 @@ void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM
|
||||
buf[buf_len - 1] = '\0';
|
||||
}
|
||||
|
||||
|
||||
static void copy_time(int* dst_flag, int32_t* dst_time, double* dst_time_s, int* src_flag, int32_t* src_time, double* src_time_s) {
|
||||
if (!*src_flag)
|
||||
return;
|
||||
*dst_flag = 1;
|
||||
*dst_time = *src_time;
|
||||
*dst_time_s = *src_time_s;
|
||||
}
|
||||
|
||||
//todo reuse in txtp?
|
||||
static void load_default_config(play_config_t* def, play_config_t* tcfg) {
|
||||
|
||||
/* loop limit: txtp #L > txtp #l > player #L > player #l */
|
||||
if (tcfg->play_forever) {
|
||||
def->play_forever = 1;
|
||||
def->ignore_loop = 0;
|
||||
}
|
||||
if (tcfg->loop_count_set) {
|
||||
def->loop_count = tcfg->loop_count;
|
||||
def->loop_count_set = 1;
|
||||
def->ignore_loop = 0;
|
||||
if (!tcfg->play_forever)
|
||||
def->play_forever = 0;
|
||||
}
|
||||
|
||||
/* fade priority: #F > #f, #d */
|
||||
if (tcfg->ignore_fade) {
|
||||
def->ignore_fade = 1;
|
||||
}
|
||||
if (tcfg->fade_delay_set) {
|
||||
def->fade_delay = tcfg->fade_delay;
|
||||
def->fade_delay_set = 1;
|
||||
}
|
||||
if (tcfg->fade_time_set) {
|
||||
def->fade_time = tcfg->fade_time;
|
||||
def->fade_time_set = 1;
|
||||
}
|
||||
|
||||
/* loop priority: #i > #e > #E (respect player's ignore too) */
|
||||
if (tcfg->really_force_loop) {
|
||||
//def->ignore_loop = 0;
|
||||
def->force_loop = 0;
|
||||
def->really_force_loop = 1;
|
||||
}
|
||||
if (tcfg->force_loop) {
|
||||
//def->ignore_loop = 0;
|
||||
def->force_loop = 1;
|
||||
def->really_force_loop = 0;
|
||||
}
|
||||
if (tcfg->ignore_loop) {
|
||||
def->ignore_loop = 1;
|
||||
def->force_loop = 0;
|
||||
def->really_force_loop = 0;
|
||||
}
|
||||
|
||||
copy_time(&def->pad_begin_set, &def->pad_begin, &def->pad_begin_s, &tcfg->pad_begin_set, &tcfg->pad_begin, &tcfg->pad_begin_s);
|
||||
copy_time(&def->pad_end_set, &def->pad_end, &def->pad_end_s, &tcfg->pad_end_set, &tcfg->pad_end, &tcfg->pad_end_s);
|
||||
copy_time(&def->trim_begin_set, &def->trim_begin, &def->trim_begin_s, &tcfg->trim_begin_set, &tcfg->trim_begin, &tcfg->trim_begin_s);
|
||||
copy_time(&def->trim_end_set, &def->trim_end, &def->trim_end_s, &tcfg->trim_end_set, &tcfg->trim_end, &tcfg->trim_end_s);
|
||||
copy_time(&def->body_time_set, &def->body_time, &def->body_time_s, &tcfg->body_time_set, &tcfg->body_time, &tcfg->body_time_s);
|
||||
|
||||
def->is_mini_txtp = tcfg->is_mini_txtp;
|
||||
def->is_txtp = tcfg->is_txtp;
|
||||
}
|
||||
|
||||
static void load_player_config(play_config_t* def, vgmstream_cfg_t* vcfg) {
|
||||
def->play_forever = vcfg->play_forever;
|
||||
def->ignore_loop = vcfg->ignore_loop;
|
||||
def->force_loop = vcfg->force_loop;
|
||||
def->really_force_loop = vcfg->really_force_loop;
|
||||
def->ignore_fade = vcfg->ignore_fade;
|
||||
|
||||
def->loop_count = vcfg->loop_count;
|
||||
def->loop_count_set = 1;
|
||||
def->fade_delay = vcfg->fade_delay;
|
||||
def->fade_delay_set = 1;
|
||||
def->fade_time = vcfg->fade_time;
|
||||
def->fade_time_set = 1;
|
||||
}
|
||||
|
||||
void vgmstream_apply_config(VGMSTREAM* vgmstream, vgmstream_cfg_t* vcfg) {
|
||||
play_config_t defs = {0};
|
||||
play_config_t* def = &defs; /* for convenience... */
|
||||
play_config_t* tcfg = &vgmstream->config;
|
||||
|
||||
|
||||
load_player_config(def, vcfg);
|
||||
def->config_set = 1;
|
||||
|
||||
if (!vcfg->disable_config_override)
|
||||
load_default_config(def, tcfg);
|
||||
|
||||
if (!vcfg->allow_play_forever)
|
||||
def->play_forever = 0;
|
||||
|
||||
/* copy final config back */
|
||||
*tcfg = *def;
|
||||
|
||||
vgmstream->config_enabled = def->config_set;
|
||||
setup_state_vgmstream(vgmstream);
|
||||
}
|
||||
|
||||
/* ****************************************** */
|
||||
/* TAGS: loads key=val tags from a file */
|
||||
/* ****************************************** */
|
||||
|
||||
#define VGMSTREAM_TAGS_LINE_MAX 2048
|
||||
|
||||
/* opaque tag state */
|
||||
struct VGMSTREAM_TAGS {
|
||||
/* extracted output */
|
||||
char key[VGMSTREAM_TAGS_LINE_MAX];
|
||||
char val[VGMSTREAM_TAGS_LINE_MAX];
|
||||
|
||||
/* file to find tags for */
|
||||
int targetname_len;
|
||||
char targetname[VGMSTREAM_TAGS_LINE_MAX];
|
||||
/* path of targetname */
|
||||
char targetpath[VGMSTREAM_TAGS_LINE_MAX];
|
||||
|
||||
/* tag section for filename (see comments below) */
|
||||
int section_found;
|
||||
off_t section_start;
|
||||
off_t section_end;
|
||||
off_t offset;
|
||||
|
||||
/* commands */
|
||||
int autotrack_on;
|
||||
int autotrack_written;
|
||||
int track_count;
|
||||
int exact_match;
|
||||
|
||||
int autoalbum_on;
|
||||
int autoalbum_written;
|
||||
};
|
||||
|
||||
|
||||
static void tags_clean(VGMSTREAM_TAGS* tag) {
|
||||
int i;
|
||||
int val_len = strlen(tag->val);
|
||||
|
||||
/* remove trailing spaces */
|
||||
for (i = val_len - 1; i > 0; i--) {
|
||||
if (tag->val[i] != ' ')
|
||||
break;
|
||||
tag->val[i] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val) {
|
||||
VGMSTREAM_TAGS* tags = malloc(sizeof(VGMSTREAM_TAGS));
|
||||
if (!tags) goto fail;
|
||||
|
||||
*tag_key = tags->key;
|
||||
*tag_val = tags->val;
|
||||
|
||||
return tags;
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void vgmstream_tags_close(VGMSTREAM_TAGS *tags) {
|
||||
free(tags);
|
||||
}
|
||||
|
||||
/* Find next tag and return 1 if found.
|
||||
*
|
||||
* Tags can be "global" @TAGS, "command" $TAGS, and "file" %TAGS for a target filename.
|
||||
* To extract tags we must find either global tags, or the filename's tag "section"
|
||||
* where tags apply: (# @TAGS ) .. (other_filename) ..(# %TAGS section).. (target_filename).
|
||||
* When a new "other_filename" is found that offset is marked as section_start, and when
|
||||
* target_filename is found it's marked as section_end. Then we can begin extracting tags
|
||||
* within that section, until all tags are exhausted. Global tags are extracted as found,
|
||||
* so they always go first, also meaning any tags after file's section are ignored.
|
||||
* Command tags have special meanings and are output after all section tags. */
|
||||
int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
|
||||
off_t file_size = get_streamfile_size(tagfile);
|
||||
char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0};
|
||||
char line[VGMSTREAM_TAGS_LINE_MAX];
|
||||
int ok, bytes_read, line_ok, n1,n2;
|
||||
|
||||
if (!tags)
|
||||
return 0;
|
||||
|
||||
/* prepare file start and skip BOM if needed */
|
||||
if (tags->offset == 0) {
|
||||
if ((uint16_t)read_16bitLE(0x00, tagfile) == 0xFFFE ||
|
||||
(uint16_t)read_16bitLE(0x00, tagfile) == 0xFEFF) {
|
||||
tags->offset = 0x02;
|
||||
if (tags->section_start == 0)
|
||||
tags->section_start = 0x02;
|
||||
}
|
||||
else if (((uint32_t)read_32bitBE(0x00, tagfile) & 0xFFFFFF00) == 0xEFBBBF00) {
|
||||
tags->offset = 0x03;
|
||||
if (tags->section_start == 0)
|
||||
tags->section_start = 0x03;
|
||||
}
|
||||
}
|
||||
|
||||
/* read lines */
|
||||
while (tags->offset <= file_size) {
|
||||
|
||||
/* after section: no more tags to extract */
|
||||
if (tags->section_found && tags->offset >= tags->section_end) {
|
||||
|
||||
/* write extra tags after all regular tags */
|
||||
if (tags->autotrack_on && !tags->autotrack_written) {
|
||||
sprintf(tags->key, "%s", "TRACK");
|
||||
sprintf(tags->val, "%i", tags->track_count);
|
||||
tags->autotrack_written = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (tags->autoalbum_on && !tags->autoalbum_written && tags->targetpath[0] != '\0') {
|
||||
const char* path;
|
||||
|
||||
path = strrchr(tags->targetpath,'\\');
|
||||
if (!path) {
|
||||
path = strrchr(tags->targetpath,'/');
|
||||
}
|
||||
if (!path) {
|
||||
path = tags->targetpath;
|
||||
}
|
||||
|
||||
sprintf(tags->key, "%s", "ALBUM");
|
||||
sprintf(tags->val, "%s", path+1);
|
||||
tags->autoalbum_written = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
bytes_read = read_line(line, sizeof(line), tags->offset, tagfile, &line_ok);
|
||||
if (!line_ok || bytes_read == 0) goto fail;
|
||||
|
||||
tags->offset += bytes_read;
|
||||
|
||||
|
||||
if (tags->section_found) {
|
||||
/* find possible file tag */
|
||||
ok = sscanf(line, "# %%%[^%%]%% %[^\r\n] ", tags->key,tags->val); /* key with spaces */
|
||||
if (ok != 2)
|
||||
ok = sscanf(line, "# %%%[^ \t] %[^\r\n] ", tags->key,tags->val); /* key without */
|
||||
if (ok == 2) {
|
||||
tags_clean(tags);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
if (line[0] == '#') {
|
||||
/* find possible global command */
|
||||
ok = sscanf(line, "# $%n%[^ \t]%n %[^\r\n]", &n1, tags->key, &n2, tags->val);
|
||||
if (ok == 1 || ok == 2) {
|
||||
int key_len = n2 - n1;
|
||||
if (strncasecmp(tags->key, "AUTOTRACK", key_len) == 0) {
|
||||
tags->autotrack_on = 1;
|
||||
}
|
||||
else if (strncasecmp(tags->key, "AUTOALBUM", key_len) == 0) {
|
||||
tags->autoalbum_on = 1;
|
||||
}
|
||||
else if (strncasecmp(tags->key, "EXACTMATCH", key_len) == 0) {
|
||||
tags->exact_match = 1;
|
||||
}
|
||||
|
||||
continue; /* not an actual tag */
|
||||
}
|
||||
|
||||
/* find possible global tag */
|
||||
ok = sscanf(line, "# @%[^@]@ %[^\r\n]", tags->key, tags->val); /* key with spaces */
|
||||
if (ok != 2)
|
||||
ok = sscanf(line, "# @%[^ \t] %[^\r\n]", tags->key, tags->val); /* key without */
|
||||
if (ok == 2) {
|
||||
tags_clean(tags);
|
||||
return 1;
|
||||
}
|
||||
|
||||
continue; /* next line */
|
||||
}
|
||||
|
||||
/* find possible filename and section start/end
|
||||
* (.m3u seem to allow filenames with whitespaces before, make sure to trim) */
|
||||
ok = sscanf(line, " %n%[^\r\n]%n ", &n1, currentname, &n2);
|
||||
if (ok == 1) {
|
||||
int currentname_len = n2 - n1;
|
||||
int filename_found = 0;
|
||||
|
||||
/* we want to match file with the same name (case insensitive), OR a virtual .txtp with
|
||||
* the filename inside to ease creation of tag files with config, also check end char to
|
||||
* tell apart the unlikely case of having both 'bgm01.ad.txtp' and 'bgm01.adp.txtp' */
|
||||
|
||||
/* try exact match (strcasecmp works ok even for UTF-8) */
|
||||
if (currentname_len == tags->targetname_len &&
|
||||
strncasecmp(currentname, tags->targetname, currentname_len) == 0) {
|
||||
filename_found = 1;
|
||||
}
|
||||
else if (!tags->exact_match) {
|
||||
/* try tagfile is "bgm.adx" + target is "bgm.adx #(cfg) .txtp" */
|
||||
if (currentname_len < tags->targetname_len &&
|
||||
strncasecmp(currentname, tags->targetname, currentname_len) == 0 &&
|
||||
vgmstream_is_virtual_filename(tags->targetname)) {
|
||||
char c = tags->targetname[currentname_len];
|
||||
filename_found = (c==' ' || c == '.' || c == '#');
|
||||
}
|
||||
/* tagfile has "bgm.adx (...) .txtp" + target has "bgm.adx" */
|
||||
else if (tags->targetname_len < currentname_len &&
|
||||
strncasecmp(tags->targetname, currentname, tags->targetname_len) == 0 &&
|
||||
vgmstream_is_virtual_filename(currentname)) {
|
||||
char c = currentname[tags->targetname_len];
|
||||
filename_found = (c==' ' || c == '.' || c == '#');
|
||||
}
|
||||
}
|
||||
|
||||
if (filename_found) {
|
||||
/* section ok, start would be set before this (or be 0) */
|
||||
tags->section_end = tags->offset;
|
||||
tags->section_found = 1;
|
||||
tags->offset = tags->section_start;
|
||||
}
|
||||
else {
|
||||
/* mark new possible section */
|
||||
tags->section_start = tags->offset;
|
||||
}
|
||||
|
||||
tags->track_count++; /* new track found (target filename or not) */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* empty/bad line, probably */
|
||||
}
|
||||
}
|
||||
|
||||
/* may reach here if read up to file_size but no section was found */
|
||||
|
||||
fail:
|
||||
tags->key[0] = '\0';
|
||||
tags->val[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) {
|
||||
char *path;
|
||||
|
||||
if (!tags)
|
||||
return;
|
||||
|
||||
memset(tags, 0, sizeof(VGMSTREAM_TAGS));
|
||||
|
||||
//todo validate sizes and copy sensible max
|
||||
|
||||
/* get base name */
|
||||
strcpy(tags->targetpath, target_filename);
|
||||
|
||||
/* Windows CMD accepts both \\ and /, and maybe plugin uses either */
|
||||
path = strrchr(tags->targetpath,'\\');
|
||||
if (!path) {
|
||||
path = strrchr(tags->targetpath,'/');
|
||||
}
|
||||
if (path != NULL) {
|
||||
path[0] = '\0'; /* leave targetpath with path only */
|
||||
path = path+1;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
strcpy(tags->targetname, path);
|
||||
} else {
|
||||
tags->targetpath[0] = '\0';
|
||||
strcpy(tags->targetname, target_filename);
|
||||
}
|
||||
tags->targetname_len = strlen(tags->targetname);
|
||||
}
|
||||
|
||||
/* ****************************************** */
|
||||
/* MIXING: modifies vgmstream output */
|
||||
/* ****************************************** */
|
||||
|
85
src/base/streamfile_api.c
Normal file
85
src/base/streamfile_api.c
Normal file
@ -0,0 +1,85 @@
|
||||
#include "api_internal.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
/* STREAMFILE for internal use, that bridges calls to external libvgmstream_streamfile_t */
|
||||
|
||||
|
||||
static STREAMFILE* open_api_streamfile_internal(libvgmstream_streamfile_t* libsf, bool external_libsf);
|
||||
|
||||
|
||||
typedef struct {
|
||||
STREAMFILE vt;
|
||||
|
||||
libvgmstream_streamfile_t* libsf;
|
||||
bool external_libsf; //TODO: improve
|
||||
} API_STREAMFILE;
|
||||
|
||||
static size_t api_read(API_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
|
||||
void* user_data = sf->libsf->user_data;
|
||||
|
||||
sf->libsf->seek(sf->libsf->user_data, offset, LIBVGMSTREAM_STREAMFILE_SEEK_SET);
|
||||
return sf->libsf->read(user_data, dst, length);
|
||||
}
|
||||
|
||||
static size_t api_get_size(API_STREAMFILE* sf) {
|
||||
void* user_data = sf->libsf->user_data;
|
||||
|
||||
return sf->libsf->get_size(user_data);
|
||||
}
|
||||
|
||||
static offv_t api_get_offset(API_STREAMFILE* sf) {
|
||||
return 0; //sf->libsf->get_offset(sf->inner_sf); /* default */
|
||||
}
|
||||
|
||||
static void api_get_name(API_STREAMFILE* sf, char* name, size_t name_size) {
|
||||
void* user_data = sf->libsf->user_data;
|
||||
|
||||
sf->libsf->get_name(user_data, name, name_size);
|
||||
}
|
||||
|
||||
static STREAMFILE* api_open(API_STREAMFILE* sf, const char* filename, size_t buf_size) {
|
||||
libvgmstream_streamfile_t* libsf = sf->libsf->open(sf->libsf->user_data, filename);
|
||||
STREAMFILE* new_sf = open_api_streamfile_internal(libsf, false);
|
||||
|
||||
if (!new_sf) {
|
||||
libvgmstream_streamfile_close(libsf);
|
||||
}
|
||||
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
static void api_close(API_STREAMFILE* sf) {
|
||||
if (sf && !sf->external_libsf) {
|
||||
sf->libsf->close(sf->libsf);
|
||||
}
|
||||
free(sf);
|
||||
}
|
||||
|
||||
static STREAMFILE* open_api_streamfile_internal(libvgmstream_streamfile_t* libsf, bool external_libsf) {
|
||||
API_STREAMFILE* this_sf = NULL;
|
||||
|
||||
if (!libsf)
|
||||
return NULL;
|
||||
|
||||
this_sf = calloc(1, sizeof(API_STREAMFILE));
|
||||
if (!this_sf) return NULL;
|
||||
|
||||
/* set callbacks and internals */
|
||||
this_sf->vt.read = (void*)api_read;
|
||||
this_sf->vt.get_size = (void*)api_get_size;
|
||||
this_sf->vt.get_offset = (void*)api_get_offset;
|
||||
this_sf->vt.get_name = (void*)api_get_name;
|
||||
this_sf->vt.open = (void*)api_open;
|
||||
this_sf->vt.close = (void*)api_close;
|
||||
//this_sf->vt.stream_index = sf->stream_index;
|
||||
|
||||
this_sf->libsf = libsf;
|
||||
this_sf->external_libsf = external_libsf;
|
||||
|
||||
return &this_sf->vt;
|
||||
}
|
||||
|
||||
STREAMFILE* open_api_streamfile(libvgmstream_streamfile_t* libsf) {
|
||||
return open_api_streamfile_internal(libsf, true);
|
||||
}
|
||||
|
||||
#endif
|
150
src/base/streamfile_buffer.c
Normal file
150
src/base/streamfile_buffer.c
Normal file
@ -0,0 +1,150 @@
|
||||
#include "../streamfile.h"
|
||||
#include "../util/log.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
STREAMFILE vt;
|
||||
|
||||
STREAMFILE* inner_sf;
|
||||
offv_t offset; /* last read offset (info) */
|
||||
offv_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 */
|
||||
} BUFFER_STREAMFILE;
|
||||
|
||||
|
||||
static size_t buffer_read(BUFFER_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
|
||||
size_t read_total = 0;
|
||||
|
||||
if (!dst || length <= 0 || offset < 0)
|
||||
return 0;
|
||||
|
||||
/* is the part of the requested length in the buffer? */
|
||||
if (offset >= sf->buf_offset && offset < sf->buf_offset + sf->valid_size) {
|
||||
size_t buf_limit;
|
||||
int buf_into = (int)(offset - sf->buf_offset);
|
||||
|
||||
buf_limit = sf->valid_size - buf_into;
|
||||
if (buf_limit > length)
|
||||
buf_limit = length;
|
||||
|
||||
memcpy(dst, sf->buf + buf_into, buf_limit);
|
||||
read_total += buf_limit;
|
||||
length -= buf_limit;
|
||||
offset += buf_limit;
|
||||
dst += buf_limit;
|
||||
}
|
||||
|
||||
#ifdef VGM_DEBUG_OUTPUT
|
||||
if (offset < sf->buf_offset) {
|
||||
//VGM_LOG("buffer: rebuffer, requested %x vs %x (sf %x)\n", (uint32_t)offset, (uint32_t)sf->buf_offset, (uint32_t)sf);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* read the rest of the requested length */
|
||||
while (length > 0) {
|
||||
size_t buf_limit;
|
||||
|
||||
/* ignore requests at EOF */
|
||||
if (offset >= sf->file_size) {
|
||||
//offset = sf->file_size; /* seems fseek doesn't clamp offset */
|
||||
VGM_ASSERT_ONCE(offset > sf->file_size, "buffer: reading over file_size 0x%x @ 0x%x + 0x%x\n", sf->file_size, (uint32_t)offset, length);
|
||||
break;
|
||||
}
|
||||
|
||||
/* fill the buffer (offset now is beyond buf_offset) */
|
||||
sf->buf_offset = offset;
|
||||
sf->valid_size = sf->inner_sf->read(sf->inner_sf, sf->buf, sf->buf_offset, sf->buf_size);
|
||||
|
||||
/* decide how much must be read this time */
|
||||
if (length > sf->buf_size)
|
||||
buf_limit = sf->buf_size;
|
||||
else
|
||||
buf_limit = length;
|
||||
|
||||
/* give up on partial reads (EOF) */
|
||||
if (sf->valid_size < buf_limit) {
|
||||
memcpy(dst, sf->buf, sf->valid_size);
|
||||
offset += sf->valid_size;
|
||||
read_total += sf->valid_size;
|
||||
break;
|
||||
}
|
||||
|
||||
/* use the new buffer */
|
||||
memcpy(dst, sf->buf, buf_limit);
|
||||
offset += buf_limit;
|
||||
read_total += buf_limit;
|
||||
length -= buf_limit;
|
||||
dst += buf_limit;
|
||||
}
|
||||
|
||||
sf->offset = offset; /* last fread offset */
|
||||
return read_total;
|
||||
}
|
||||
|
||||
static size_t buffer_get_size(BUFFER_STREAMFILE* sf) {
|
||||
return sf->file_size; /* cache */
|
||||
}
|
||||
|
||||
static offv_t buffer_get_offset(BUFFER_STREAMFILE* sf) {
|
||||
return sf->offset; /* cache */
|
||||
}
|
||||
|
||||
static void buffer_get_name(BUFFER_STREAMFILE* sf, char* name, size_t name_size) {
|
||||
sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */
|
||||
}
|
||||
|
||||
static STREAMFILE* buffer_open(BUFFER_STREAMFILE* sf, const char* const filename, size_t buf_size) {
|
||||
STREAMFILE* new_inner_sf = sf->inner_sf->open(sf->inner_sf,filename,buf_size);
|
||||
return open_buffer_streamfile(new_inner_sf, buf_size); /* original buffer size is preferable? */
|
||||
}
|
||||
|
||||
static void buffer_close(BUFFER_STREAMFILE* sf) {
|
||||
sf->inner_sf->close(sf->inner_sf);
|
||||
free(sf->buf);
|
||||
free(sf);
|
||||
}
|
||||
|
||||
|
||||
STREAMFILE* open_buffer_streamfile(STREAMFILE* sf, size_t buf_size) {
|
||||
BUFFER_STREAMFILE* this_sf = NULL;
|
||||
|
||||
if (!sf) goto fail;
|
||||
|
||||
if (buf_size == 0)
|
||||
buf_size = STREAMFILE_DEFAULT_BUFFER_SIZE;
|
||||
|
||||
this_sf = calloc(1, sizeof(BUFFER_STREAMFILE));
|
||||
if (!this_sf) goto fail;
|
||||
|
||||
/* set callbacks and internals */
|
||||
this_sf->vt.read = (void*)buffer_read;
|
||||
this_sf->vt.get_size = (void*)buffer_get_size;
|
||||
this_sf->vt.get_offset = (void*)buffer_get_offset;
|
||||
this_sf->vt.get_name = (void*)buffer_get_name;
|
||||
this_sf->vt.open = (void*)buffer_open;
|
||||
this_sf->vt.close = (void*)buffer_close;
|
||||
this_sf->vt.stream_index = sf->stream_index;
|
||||
|
||||
this_sf->inner_sf = sf;
|
||||
this_sf->buf_size = buf_size;
|
||||
this_sf->buf = calloc(buf_size, sizeof(uint8_t));
|
||||
if (!this_sf->buf) goto fail;
|
||||
|
||||
this_sf->file_size = sf->get_size(sf);
|
||||
|
||||
return &this_sf->vt;
|
||||
|
||||
fail:
|
||||
if (this_sf) free(this_sf->buf);
|
||||
free(this_sf);
|
||||
return NULL;
|
||||
}
|
||||
STREAMFILE* open_buffer_streamfile_f(STREAMFILE* sf, size_t buffer_size) {
|
||||
STREAMFILE* new_sf = open_buffer_streamfile(sf, buffer_size);
|
||||
if (!new_sf)
|
||||
close_streamfile(sf);
|
||||
return new_sf;
|
||||
}
|
89
src/base/streamfile_clamp.c
Normal file
89
src/base/streamfile_clamp.c
Normal file
@ -0,0 +1,89 @@
|
||||
#include "../streamfile.h"
|
||||
#include "../util/vgmstream_limits.h"
|
||||
|
||||
typedef struct {
|
||||
STREAMFILE vt;
|
||||
|
||||
STREAMFILE* inner_sf;
|
||||
offv_t start;
|
||||
size_t size;
|
||||
} CLAMP_STREAMFILE;
|
||||
|
||||
static size_t clamp_read(CLAMP_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
|
||||
offv_t inner_offset = sf->start + offset;
|
||||
size_t clamp_length = length;
|
||||
|
||||
if (offset + length > sf->size) {
|
||||
if (offset >= sf->size)
|
||||
clamp_length = 0;
|
||||
else
|
||||
clamp_length = sf->size - offset;
|
||||
}
|
||||
|
||||
return sf->inner_sf->read(sf->inner_sf, dst, inner_offset, clamp_length);
|
||||
}
|
||||
|
||||
static size_t clamp_get_size(CLAMP_STREAMFILE* sf) {
|
||||
return sf->size;
|
||||
}
|
||||
|
||||
static offv_t clamp_get_offset(CLAMP_STREAMFILE* sf) {
|
||||
return sf->inner_sf->get_offset(sf->inner_sf) - sf->start;
|
||||
}
|
||||
|
||||
static void clamp_get_name(CLAMP_STREAMFILE* sf, char* name, size_t name_size) {
|
||||
sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */
|
||||
}
|
||||
|
||||
static STREAMFILE* clamp_open(CLAMP_STREAMFILE* sf, const char* const filename, size_t buf_size) {
|
||||
char original_filename[PATH_LIMIT];
|
||||
STREAMFILE* new_inner_sf = NULL;
|
||||
|
||||
new_inner_sf = sf->inner_sf->open(sf->inner_sf,filename,buf_size);
|
||||
sf->inner_sf->get_name(sf->inner_sf, original_filename, PATH_LIMIT);
|
||||
|
||||
/* detect re-opening the file */
|
||||
if (strcmp(filename, original_filename) == 0) {
|
||||
return open_clamp_streamfile(new_inner_sf, sf->start, sf->size); /* clamp again */
|
||||
} else {
|
||||
return new_inner_sf;
|
||||
}
|
||||
}
|
||||
|
||||
static void clamp_close(CLAMP_STREAMFILE* sf) {
|
||||
sf->inner_sf->close(sf->inner_sf);
|
||||
free(sf);
|
||||
}
|
||||
|
||||
|
||||
STREAMFILE* open_clamp_streamfile(STREAMFILE* sf, offv_t start, size_t size) {
|
||||
CLAMP_STREAMFILE* this_sf = NULL;
|
||||
|
||||
if (!sf || size == 0) return NULL;
|
||||
if (start + size > get_streamfile_size(sf)) return NULL;
|
||||
|
||||
this_sf = calloc(1, sizeof(CLAMP_STREAMFILE));
|
||||
if (!this_sf) return NULL;
|
||||
|
||||
/* set callbacks and internals */
|
||||
this_sf->vt.read = (void*)clamp_read;
|
||||
this_sf->vt.get_size = (void*)clamp_get_size;
|
||||
this_sf->vt.get_offset = (void*)clamp_get_offset;
|
||||
this_sf->vt.get_name = (void*)clamp_get_name;
|
||||
this_sf->vt.open = (void*)clamp_open;
|
||||
this_sf->vt.close = (void*)clamp_close;
|
||||
this_sf->vt.stream_index = sf->stream_index;
|
||||
|
||||
this_sf->inner_sf = sf;
|
||||
this_sf->start = start;
|
||||
this_sf->size = size;
|
||||
|
||||
return &this_sf->vt;
|
||||
}
|
||||
|
||||
STREAMFILE* open_clamp_streamfile_f(STREAMFILE* sf, offv_t start, size_t size) {
|
||||
STREAMFILE* new_sf = open_clamp_streamfile(sf, start, size);
|
||||
if (!new_sf)
|
||||
close_streamfile(sf);
|
||||
return new_sf;
|
||||
}
|
99
src/base/streamfile_fakename.c
Normal file
99
src/base/streamfile_fakename.c
Normal file
@ -0,0 +1,99 @@
|
||||
#include "../streamfile.h"
|
||||
#include "../util/vgmstream_limits.h"
|
||||
|
||||
typedef struct {
|
||||
STREAMFILE vt;
|
||||
|
||||
STREAMFILE* inner_sf;
|
||||
char fakename[PATH_LIMIT];
|
||||
int fakename_len;
|
||||
} FAKENAME_STREAMFILE;
|
||||
|
||||
static size_t fakename_read(FAKENAME_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
|
||||
return sf->inner_sf->read(sf->inner_sf, dst, offset, length); /* default */
|
||||
}
|
||||
|
||||
static size_t fakename_get_size(FAKENAME_STREAMFILE* sf) {
|
||||
return sf->inner_sf->get_size(sf->inner_sf); /* default */
|
||||
}
|
||||
|
||||
static offv_t fakename_get_offset(FAKENAME_STREAMFILE* sf) {
|
||||
return sf->inner_sf->get_offset(sf->inner_sf); /* default */
|
||||
}
|
||||
|
||||
static void fakename_get_name(FAKENAME_STREAMFILE* sf, char* name, size_t name_size) {
|
||||
int copy_size = sf->fakename_len + 1;
|
||||
if (copy_size > name_size)
|
||||
copy_size = name_size;
|
||||
memcpy(name, sf->fakename, copy_size);
|
||||
name[copy_size - 1] = '\0';
|
||||
}
|
||||
|
||||
static STREAMFILE* fakename_open(FAKENAME_STREAMFILE* sf, const char* const filename, size_t buf_size) {
|
||||
/* detect re-opening the file */
|
||||
if (strcmp(filename, sf->fakename) == 0) {
|
||||
STREAMFILE* new_inner_sf;
|
||||
char original_filename[PATH_LIMIT];
|
||||
|
||||
sf->inner_sf->get_name(sf->inner_sf, original_filename, PATH_LIMIT);
|
||||
new_inner_sf = sf->inner_sf->open(sf->inner_sf, original_filename, buf_size);
|
||||
return open_fakename_streamfile(new_inner_sf, sf->fakename, NULL);
|
||||
}
|
||||
else {
|
||||
return sf->inner_sf->open(sf->inner_sf, filename, buf_size);
|
||||
}
|
||||
}
|
||||
|
||||
static void fakename_close(FAKENAME_STREAMFILE* sf) {
|
||||
sf->inner_sf->close(sf->inner_sf);
|
||||
free(sf);
|
||||
}
|
||||
|
||||
|
||||
STREAMFILE* open_fakename_streamfile(STREAMFILE* sf, const char* fakename, const char* fakeext) {
|
||||
FAKENAME_STREAMFILE* this_sf = NULL;
|
||||
|
||||
if (!sf || (!fakename && !fakeext)) return NULL;
|
||||
|
||||
this_sf = calloc(1, sizeof(FAKENAME_STREAMFILE));
|
||||
if (!this_sf) return NULL;
|
||||
|
||||
/* set callbacks and internals */
|
||||
this_sf->vt.read = (void*)fakename_read;
|
||||
this_sf->vt.get_size = (void*)fakename_get_size;
|
||||
this_sf->vt.get_offset = (void*)fakename_get_offset;
|
||||
this_sf->vt.get_name = (void*)fakename_get_name;
|
||||
this_sf->vt.open = (void*)fakename_open;
|
||||
this_sf->vt.close = (void*)fakename_close;
|
||||
this_sf->vt.stream_index = sf->stream_index;
|
||||
|
||||
this_sf->inner_sf = sf;
|
||||
|
||||
/* copy passed name or retain current, and swap extension if expected */
|
||||
if (fakename) {
|
||||
strcpy(this_sf->fakename, fakename);
|
||||
} else {
|
||||
sf->get_name(sf, this_sf->fakename, PATH_LIMIT);
|
||||
}
|
||||
|
||||
if (fakeext) {
|
||||
char* ext = strrchr(this_sf->fakename, '.');
|
||||
if (ext != NULL) {
|
||||
ext[1] = '\0'; /* truncate past dot */
|
||||
} else {
|
||||
strcat(this_sf->fakename, "."); /* no extension = add dot */
|
||||
}
|
||||
strcat(this_sf->fakename, fakeext);
|
||||
}
|
||||
|
||||
this_sf->fakename_len = strlen(this_sf->fakename);
|
||||
|
||||
return &this_sf->vt;
|
||||
}
|
||||
|
||||
STREAMFILE* open_fakename_streamfile_f(STREAMFILE* sf, const char* fakename, const char* fakeext) {
|
||||
STREAMFILE* new_sf = open_fakename_streamfile(sf, fakename, fakeext);
|
||||
if (!new_sf)
|
||||
close_streamfile(sf);
|
||||
return new_sf;
|
||||
}
|
104
src/base/streamfile_io.c
Normal file
104
src/base/streamfile_io.c
Normal file
@ -0,0 +1,104 @@
|
||||
#include "../streamfile.h"
|
||||
|
||||
typedef struct {
|
||||
STREAMFILE vt;
|
||||
|
||||
STREAMFILE* inner_sf;
|
||||
void* data; /* state for custom reads, malloc'ed + copied on open (to re-open streamfiles cleanly) */
|
||||
size_t data_size;
|
||||
size_t (*read_callback)(STREAMFILE*, uint8_t*, off_t, size_t, void*); /* custom read to modify data before copying into buffer */
|
||||
size_t (*size_callback)(STREAMFILE*, void*); /* size when custom reads make data smaller/bigger than underlying streamfile */
|
||||
int (*init_callback)(STREAMFILE*, void*); /* init the data struct members somehow, return >= 0 if ok */
|
||||
void (*close_callback)(STREAMFILE*, void*); /* close the data struct members somehow */
|
||||
/* read doesn't use offv_t since callbacks would need to be modified */
|
||||
} IO_STREAMFILE;
|
||||
|
||||
static size_t io_read(IO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
|
||||
return sf->read_callback(sf->inner_sf, dst, (off_t)offset, length, sf->data);
|
||||
}
|
||||
|
||||
static size_t io_get_size(IO_STREAMFILE* sf) {
|
||||
if (sf->size_callback)
|
||||
return sf->size_callback(sf->inner_sf, sf->data);
|
||||
else
|
||||
return sf->inner_sf->get_size(sf->inner_sf); /* default */
|
||||
}
|
||||
|
||||
static offv_t io_get_offset(IO_STREAMFILE* sf) {
|
||||
return sf->inner_sf->get_offset(sf->inner_sf); /* default */
|
||||
}
|
||||
|
||||
static void io_get_name(IO_STREAMFILE* sf, char* name, size_t name_size) {
|
||||
sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */
|
||||
}
|
||||
|
||||
static STREAMFILE* io_open(IO_STREAMFILE* sf, const char* const filename, size_t buf_size) {
|
||||
STREAMFILE* new_inner_sf = sf->inner_sf->open(sf->inner_sf,filename,buf_size);
|
||||
return open_io_streamfile_ex(new_inner_sf, sf->data, sf->data_size, sf->read_callback, sf->size_callback, sf->init_callback, sf->close_callback);
|
||||
}
|
||||
|
||||
static void io_close(IO_STREAMFILE* sf) {
|
||||
if (sf->close_callback)
|
||||
sf->close_callback(sf->inner_sf, sf->data);
|
||||
sf->inner_sf->close(sf->inner_sf);
|
||||
free(sf->data);
|
||||
free(sf);
|
||||
}
|
||||
|
||||
|
||||
STREAMFILE* open_io_streamfile_ex(STREAMFILE* sf, void* data, size_t data_size, void* read_callback, void* size_callback, void* init_callback, void* close_callback) {
|
||||
IO_STREAMFILE* this_sf = NULL;
|
||||
|
||||
if (!sf) goto fail;
|
||||
if ((data && !data_size) || (!data && data_size)) goto fail;
|
||||
|
||||
this_sf = calloc(1, sizeof(IO_STREAMFILE));
|
||||
if (!this_sf) goto fail;
|
||||
|
||||
/* set callbacks and internals */
|
||||
this_sf->vt.read = (void*)io_read;
|
||||
this_sf->vt.get_size = (void*)io_get_size;
|
||||
this_sf->vt.get_offset = (void*)io_get_offset;
|
||||
this_sf->vt.get_name = (void*)io_get_name;
|
||||
this_sf->vt.open = (void*)io_open;
|
||||
this_sf->vt.close = (void*)io_close;
|
||||
this_sf->vt.stream_index = sf->stream_index;
|
||||
|
||||
this_sf->inner_sf = sf;
|
||||
if (data) {
|
||||
this_sf->data = malloc(data_size);
|
||||
if (!this_sf->data) goto fail;
|
||||
memcpy(this_sf->data, data, data_size);
|
||||
}
|
||||
this_sf->data_size = data_size;
|
||||
this_sf->read_callback = read_callback;
|
||||
this_sf->size_callback = size_callback;
|
||||
this_sf->init_callback = init_callback;
|
||||
this_sf->close_callback = close_callback;
|
||||
|
||||
if (this_sf->init_callback) {
|
||||
int ok = this_sf->init_callback(this_sf->inner_sf, this_sf->data);
|
||||
if (ok < 0) goto fail;
|
||||
}
|
||||
|
||||
return &this_sf->vt;
|
||||
|
||||
fail:
|
||||
if (this_sf) free(this_sf->data);
|
||||
free(this_sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
STREAMFILE* open_io_streamfile_ex_f(STREAMFILE* sf, void* data, size_t data_size, void* read_callback, void* size_callback, void* init_callback, void* close_callback) {
|
||||
STREAMFILE* new_sf = open_io_streamfile_ex(sf, data, data_size, read_callback, size_callback, init_callback, close_callback);
|
||||
if (!new_sf)
|
||||
close_streamfile(sf);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
STREAMFILE* open_io_streamfile(STREAMFILE* sf, void* data, size_t data_size, void* read_callback, void* size_callback) {
|
||||
return open_io_streamfile_ex(sf, data, data_size, read_callback, size_callback, NULL, NULL);
|
||||
}
|
||||
STREAMFILE* open_io_streamfile_f(STREAMFILE* sf, void* data, size_t data_size, void* read_callback, void* size_callback) {
|
||||
return open_io_streamfile_ex_f(sf, data, data_size, read_callback, size_callback, NULL, NULL);
|
||||
}
|
169
src/base/streamfile_multifile.c
Normal file
169
src/base/streamfile_multifile.c
Normal file
@ -0,0 +1,169 @@
|
||||
#include "../streamfile.h"
|
||||
#include "../util/vgmstream_limits.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
STREAMFILE vt;
|
||||
|
||||
STREAMFILE** inner_sfs;
|
||||
size_t inner_sfs_size;
|
||||
size_t *sizes;
|
||||
offv_t size;
|
||||
offv_t offset;
|
||||
} MULTIFILE_STREAMFILE;
|
||||
|
||||
static size_t multifile_read(MULTIFILE_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
|
||||
int i, segment = 0;
|
||||
offv_t segment_offset = 0;
|
||||
size_t done = 0;
|
||||
|
||||
if (offset > sf->size) {
|
||||
sf->offset = sf->size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* map external offset to multifile offset */
|
||||
for (i = 0; i < sf->inner_sfs_size; i++) {
|
||||
size_t segment_size = sf->sizes[i];
|
||||
/* check if offset falls in this segment */
|
||||
if (offset >= segment_offset && offset < segment_offset + segment_size) {
|
||||
segment = i;
|
||||
segment_offset = offset - segment_offset;
|
||||
break;
|
||||
}
|
||||
|
||||
segment_offset += segment_size;
|
||||
}
|
||||
|
||||
/* reads can span multiple segments */
|
||||
while(done < length) {
|
||||
if (segment >= sf->inner_sfs_size) /* over last segment, not fully done */
|
||||
break;
|
||||
/* reads over segment size are ok, will return smaller value and continue next segment */
|
||||
done += sf->inner_sfs[segment]->read(sf->inner_sfs[segment], dst + done, segment_offset, length - done);
|
||||
segment++;
|
||||
segment_offset = 0;
|
||||
}
|
||||
|
||||
sf->offset = offset + done;
|
||||
return done;
|
||||
}
|
||||
|
||||
static size_t multifile_get_size(MULTIFILE_STREAMFILE* sf) {
|
||||
return sf->size;
|
||||
}
|
||||
|
||||
static offv_t multifile_get_offset(MULTIFILE_STREAMFILE* sf) {
|
||||
return sf->offset;
|
||||
}
|
||||
|
||||
static void multifile_get_name(MULTIFILE_STREAMFILE* sf, char* name, size_t name_size) {
|
||||
sf->inner_sfs[0]->get_name(sf->inner_sfs[0], name, name_size);
|
||||
}
|
||||
|
||||
static STREAMFILE* multifile_open(MULTIFILE_STREAMFILE* sf, const char* const filename, size_t buf_size) {
|
||||
char original_filename[PATH_LIMIT];
|
||||
STREAMFILE* new_sf = NULL;
|
||||
STREAMFILE** new_inner_sfs = NULL;
|
||||
int i;
|
||||
|
||||
sf->inner_sfs[0]->get_name(sf->inner_sfs[0], original_filename, PATH_LIMIT);
|
||||
|
||||
/* detect re-opening the file */
|
||||
if (strcmp(filename, original_filename) == 0) { /* same multifile */
|
||||
new_inner_sfs = calloc(sf->inner_sfs_size, sizeof(STREAMFILE*));
|
||||
if (!new_inner_sfs) goto fail;
|
||||
|
||||
for (i = 0; i < sf->inner_sfs_size; i++) {
|
||||
sf->inner_sfs[i]->get_name(sf->inner_sfs[i], original_filename, PATH_LIMIT);
|
||||
new_inner_sfs[i] = sf->inner_sfs[i]->open(sf->inner_sfs[i], original_filename, buf_size);
|
||||
if (!new_inner_sfs[i]) goto fail;
|
||||
}
|
||||
|
||||
new_sf = open_multifile_streamfile(new_inner_sfs, sf->inner_sfs_size);
|
||||
if (!new_sf) goto fail;
|
||||
|
||||
free(new_inner_sfs);
|
||||
return new_sf;
|
||||
}
|
||||
else {
|
||||
return sf->inner_sfs[0]->open(sf->inner_sfs[0], filename, buf_size); /* regular file */
|
||||
}
|
||||
|
||||
fail:
|
||||
if (new_inner_sfs) {
|
||||
for (i = 0; i < sf->inner_sfs_size; i++)
|
||||
close_streamfile(new_inner_sfs[i]);
|
||||
}
|
||||
free(new_inner_sfs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void multifile_close(MULTIFILE_STREAMFILE* sf) {
|
||||
int i;
|
||||
for (i = 0; i < sf->inner_sfs_size; i++) {
|
||||
for (i = 0; i < sf->inner_sfs_size; i++) {
|
||||
close_streamfile(sf->inner_sfs[i]);
|
||||
}
|
||||
}
|
||||
free(sf->inner_sfs);
|
||||
free(sf->sizes);
|
||||
free(sf);
|
||||
}
|
||||
|
||||
|
||||
STREAMFILE* open_multifile_streamfile(STREAMFILE** sfs, size_t sfs_size) {
|
||||
MULTIFILE_STREAMFILE* this_sf = NULL;
|
||||
int i;
|
||||
|
||||
if (!sfs || !sfs_size) return NULL;
|
||||
|
||||
for (i = 0; i < sfs_size; i++) {
|
||||
if (!sfs[i]) return NULL;
|
||||
}
|
||||
|
||||
this_sf = calloc(1, sizeof(MULTIFILE_STREAMFILE));
|
||||
if (!this_sf) goto fail;
|
||||
|
||||
/* set callbacks and internals */
|
||||
this_sf->vt.read = (void*)multifile_read;
|
||||
this_sf->vt.get_size = (void*)multifile_get_size;
|
||||
this_sf->vt.get_offset = (void*)multifile_get_offset;
|
||||
this_sf->vt.get_name = (void*)multifile_get_name;
|
||||
this_sf->vt.open = (void*)multifile_open;
|
||||
this_sf->vt.close = (void*)multifile_close;
|
||||
this_sf->vt.stream_index = sfs[0]->stream_index;
|
||||
|
||||
this_sf->inner_sfs_size = sfs_size;
|
||||
this_sf->inner_sfs = calloc(sfs_size, sizeof(STREAMFILE*));
|
||||
if (!this_sf->inner_sfs) goto fail;
|
||||
this_sf->sizes = calloc(sfs_size, sizeof(size_t));
|
||||
if (!this_sf->sizes) goto fail;
|
||||
|
||||
for (i = 0; i < this_sf->inner_sfs_size; i++) {
|
||||
this_sf->inner_sfs[i] = sfs[i];
|
||||
this_sf->sizes[i] = sfs[i]->get_size(sfs[i]);
|
||||
this_sf->size += this_sf->sizes[i];
|
||||
}
|
||||
|
||||
return &this_sf->vt;
|
||||
|
||||
fail:
|
||||
if (this_sf) {
|
||||
free(this_sf->inner_sfs);
|
||||
free(this_sf->sizes);
|
||||
}
|
||||
free(this_sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
STREAMFILE* open_multifile_streamfile_f(STREAMFILE** sfs, size_t sfs_size) {
|
||||
STREAMFILE* new_sf = open_multifile_streamfile(sfs, sfs_size);
|
||||
if (!new_sf) {
|
||||
int i;
|
||||
for (i = 0; i < sfs_size; i++) {
|
||||
close_streamfile(sfs[i]);
|
||||
}
|
||||
}
|
||||
return new_sf;
|
||||
}
|
381
src/base/streamfile_stdio.c
Normal file
381
src/base/streamfile_stdio.c
Normal file
@ -0,0 +1,381 @@
|
||||
#include "../streamfile.h"
|
||||
#include "../util/vgmstream_limits.h"
|
||||
#include "../util/log.h"
|
||||
#include "../util/sf_utils.h"
|
||||
#include "../vgmstream.h"
|
||||
|
||||
|
||||
/* for dup/fdopen in some systems */
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
/* Enables a minor optimization when reopening file descriptors.
|
||||
* Some systems/compilers have issues though, and dupe'd FILEs may fread garbage data in rare cases,
|
||||
* possibly due to underlying buffers that get shared/thrashed by dup(). Seen for example in some .HPS and Ubi
|
||||
* bigfiles (some later MSVC versions) or PS2 .RSD (Mac), where 2nd channel = 2nd SF reads garbage at some points.
|
||||
*
|
||||
* Keep it for other systems since this is (probably) kinda useful, though a more sensible approach would be
|
||||
* redoing SF/FILE/buffer handling to avoid re-opening as much. */
|
||||
#if !defined (_MSC_VER) && !defined (__ANDROID__) && !defined (__APPLE__)
|
||||
#define USE_STDIO_FDUP 1
|
||||
#endif
|
||||
|
||||
/* For (rarely needed) +2GB file support we use fseek64/ftell64. Those are usually available
|
||||
* but may depend on compiler.
|
||||
* - MSVC: +VS2008 should work
|
||||
* - GCC/MingW: should be available
|
||||
* - GCC/Linux: should be available but some systems may need __USE_FILE_OFFSET64,
|
||||
* that we (probably) don't want since that turns off_t to off64_t
|
||||
* - Clang: seems only defined on Linux/GNU environments, somehow emscripten is out
|
||||
* (unsure about Clang Win since apparently they define _MSC_VER)
|
||||
* - Android: API +24 if not using __USE_FILE_OFFSET64
|
||||
* Not sure if fopen64 is needed in some cases.
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER) //&& defined(__MSVCRT__)
|
||||
/* MSVC fixes (MinG64 seems to set MSVCRT too, but we want it below) */
|
||||
#include <io.h>
|
||||
|
||||
#define fopen_v fopen
|
||||
#if (_MSC_VER >= 1400)
|
||||
#define fseek_v _fseeki64
|
||||
#define ftell_v _ftelli64
|
||||
#else
|
||||
#define fseek_v fseek
|
||||
#define ftell_v ftell
|
||||
#endif
|
||||
|
||||
#ifdef fileno
|
||||
#undef fileno
|
||||
#endif
|
||||
#define fileno _fileno
|
||||
#define fdopen _fdopen
|
||||
#define dup _dup
|
||||
|
||||
//#ifndef off64_t
|
||||
// #define off_t/off64_t __int64
|
||||
//#endif
|
||||
|
||||
#elif defined(VGMSTREAM_USE_IO64) || defined(__MINGW32__) || defined(__MINGW64__)
|
||||
/* force, or known to work */
|
||||
#define fopen_v fopen
|
||||
#define fseek_v fseeko64 //fseeko
|
||||
#define ftell_v ftello64 //ftello
|
||||
|
||||
#elif defined(XBMC) || defined(__EMSCRIPTEN__) || defined (__ANDROID__)
|
||||
/* may depend on version */
|
||||
#define fopen_v fopen
|
||||
#define fseek_v fseek
|
||||
#define ftell_v ftell
|
||||
|
||||
#else
|
||||
/* other Linux systems may already use off64_t in fseeko/ftello? */
|
||||
#define fopen_v fopen
|
||||
#define fseek_v fseeko
|
||||
#define ftell_v ftello
|
||||
#endif
|
||||
|
||||
|
||||
/* a STREAMFILE that operates via standard IO using a buffer */
|
||||
typedef struct {
|
||||
STREAMFILE vt; /* callbacks */
|
||||
|
||||
FILE* infile; /* actual FILE */
|
||||
char name[PATH_LIMIT]; /* FILE filename */
|
||||
int name_len; /* cache */
|
||||
offv_t offset; /* last read offset (info) */
|
||||
offv_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 */
|
||||
} STDIO_STREAMFILE;
|
||||
|
||||
static STREAMFILE* open_stdio_streamfile_buffer(const char* const filename, size_t buf_size);
|
||||
static STREAMFILE* open_stdio_streamfile_buffer_by_file(FILE *infile, const char* const filename, size_t buf_size);
|
||||
|
||||
static size_t stdio_read(STDIO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
|
||||
size_t read_total = 0;
|
||||
|
||||
if (/*!sf->infile ||*/ !dst || length <= 0 || offset < 0)
|
||||
return 0;
|
||||
|
||||
//;VGM_LOG("stdio: read %lx + %x (buf %lx + %x)\n", offset, length, sf->buf_offset, sf->valid_size, sf->buf_size);
|
||||
|
||||
/* is the part of the requested length in the buffer? */
|
||||
if (offset >= sf->buf_offset && offset < sf->buf_offset + sf->valid_size) {
|
||||
size_t buf_limit;
|
||||
int buf_into = (int)(offset - sf->buf_offset);
|
||||
|
||||
buf_limit = sf->valid_size - buf_into;
|
||||
if (buf_limit > length)
|
||||
buf_limit = length;
|
||||
|
||||
//;VGM_LOG("stdio: copy buf %lx + %x (+ %x) (buf %lx + %x)\n", offset, length_to_read, (length - length_to_read), sf->buf_offset, sf->valid_size);
|
||||
|
||||
memcpy(dst, sf->buf + buf_into, buf_limit);
|
||||
read_total += buf_limit;
|
||||
length -= buf_limit;
|
||||
offset += buf_limit;
|
||||
dst += buf_limit;
|
||||
}
|
||||
|
||||
#ifdef VGM_DEBUG_OUTPUT
|
||||
if (offset < sf->buf_offset && length > 0) {
|
||||
//VGM_LOG("stdio: rebuffer, requested %x vs %x (sf %x)\n", (uint32_t)offset, (uint32_t)sf->buf_offset, (uint32_t)sf);
|
||||
//sf->rebuffer++;
|
||||
//if (rebuffer > N) ...
|
||||
}
|
||||
#endif
|
||||
|
||||
/* possible if all data was copied to buf and FD closed */
|
||||
if (!sf->infile)
|
||||
return read_total;
|
||||
|
||||
/* read the rest of the requested length */
|
||||
while (length > 0) {
|
||||
size_t length_to_read;
|
||||
|
||||
/* ignore requests at EOF */
|
||||
if (offset >= sf->file_size) {
|
||||
//offset = sf->file_size; /* seems fseek doesn't clamp offset */
|
||||
VGM_ASSERT_ONCE(offset > sf->file_size, "STDIO: reading over file_size 0x%x @ 0x%x + 0x%x\n", sf->file_size, (uint32_t)offset, length);
|
||||
break;
|
||||
}
|
||||
|
||||
/* position to new offset */
|
||||
if (fseek_v(sf->infile, offset, SEEK_SET)) {
|
||||
break; /* this shouldn't happen in our code */
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* old workaround for USE_STDIO_FDUP bug, keep it here for a while as a reminder just in case */
|
||||
//fseek_v(sf->infile, ftell_v(sf->infile), SEEK_SET);
|
||||
#endif
|
||||
|
||||
/* fill the buffer (offset now is beyond buf_offset) */
|
||||
sf->buf_offset = offset;
|
||||
sf->valid_size = fread(sf->buf, sizeof(uint8_t), sf->buf_size, sf->infile);
|
||||
//;VGM_LOG("stdio: read buf %lx + %x\n", sf->buf_offset, sf->valid_size);
|
||||
|
||||
/* decide how much must be read this time */
|
||||
if (length > sf->buf_size)
|
||||
length_to_read = sf->buf_size;
|
||||
else
|
||||
length_to_read = length;
|
||||
|
||||
/* give up on partial reads (EOF) */
|
||||
if (sf->valid_size < length_to_read) {
|
||||
memcpy(dst, sf->buf, sf->valid_size);
|
||||
offset += sf->valid_size;
|
||||
read_total += sf->valid_size;
|
||||
break;
|
||||
}
|
||||
|
||||
/* use the new buffer */
|
||||
memcpy(dst, sf->buf, length_to_read);
|
||||
offset += length_to_read;
|
||||
read_total += length_to_read;
|
||||
length -= length_to_read;
|
||||
dst += length_to_read;
|
||||
}
|
||||
|
||||
sf->offset = offset; /* last fread offset */
|
||||
return read_total;
|
||||
}
|
||||
|
||||
static size_t stdio_get_size(STDIO_STREAMFILE* sf) {
|
||||
return sf->file_size;
|
||||
}
|
||||
|
||||
static offv_t stdio_get_offset(STDIO_STREAMFILE* sf) {
|
||||
return sf->offset;
|
||||
}
|
||||
|
||||
static void stdio_get_name(STDIO_STREAMFILE* sf, char* name, size_t name_size) {
|
||||
int copy_size = sf->name_len + 1;
|
||||
if (copy_size > name_size)
|
||||
copy_size = name_size;
|
||||
|
||||
memcpy(name, sf->name, copy_size);
|
||||
name[copy_size - 1] = '\0';
|
||||
}
|
||||
|
||||
static STREAMFILE* stdio_open(STDIO_STREAMFILE* sf, const char* const filename, size_t buf_size) {
|
||||
if (!filename)
|
||||
return NULL;
|
||||
|
||||
#ifdef USE_STDIO_FDUP
|
||||
/* minor optimization when reopening files, see comment in #define above */
|
||||
|
||||
/* if same name, duplicate the file descriptor we already have open */
|
||||
if (sf->infile && !strcmp(sf->name,filename)) {
|
||||
int new_fd;
|
||||
FILE *new_file = NULL;
|
||||
|
||||
if (((new_fd = dup(fileno(sf->infile))) >= 0) && (new_file = fdopen(new_fd, "rb"))) {
|
||||
STREAMFILE* new_sf = open_stdio_streamfile_buffer_by_file(new_file, filename, buf_size);
|
||||
if (new_sf)
|
||||
return new_sf;
|
||||
fclose(new_file);
|
||||
}
|
||||
if (new_fd >= 0 && !new_file)
|
||||
close(new_fd); /* fdopen may fail when opening too many files */
|
||||
|
||||
/* on failure just close and try the default path (which will probably fail a second time) */
|
||||
}
|
||||
#endif
|
||||
|
||||
return open_stdio_streamfile_buffer(filename, buf_size);
|
||||
}
|
||||
|
||||
static void stdio_close(STDIO_STREAMFILE* sf) {
|
||||
if (sf->infile)
|
||||
fclose(sf->infile);
|
||||
free(sf->buf);
|
||||
free(sf);
|
||||
}
|
||||
|
||||
|
||||
static STREAMFILE* open_stdio_streamfile_buffer_by_file(FILE* infile, const char* const filename, size_t buf_size) {
|
||||
uint8_t* buf = NULL;
|
||||
STDIO_STREAMFILE* this_sf = NULL;
|
||||
|
||||
if (buf_size <= 0)
|
||||
buf_size = STREAMFILE_DEFAULT_BUFFER_SIZE;
|
||||
|
||||
buf = calloc(buf_size, sizeof(uint8_t));
|
||||
if (!buf) goto fail;
|
||||
|
||||
this_sf = calloc(1, sizeof(STDIO_STREAMFILE));
|
||||
if (!this_sf) goto fail;
|
||||
|
||||
this_sf->vt.read = (void*)stdio_read;
|
||||
this_sf->vt.get_size = (void*)stdio_get_size;
|
||||
this_sf->vt.get_offset = (void*)stdio_get_offset;
|
||||
this_sf->vt.get_name = (void*)stdio_get_name;
|
||||
this_sf->vt.open = (void*)stdio_open;
|
||||
this_sf->vt.close = (void*)stdio_close;
|
||||
|
||||
this_sf->infile = infile;
|
||||
this_sf->buf_size = buf_size;
|
||||
this_sf->buf = buf;
|
||||
|
||||
this_sf->name_len = strlen(filename);
|
||||
if (this_sf->name_len >= sizeof(this_sf->name))
|
||||
goto fail;
|
||||
memcpy(this_sf->name, filename, this_sf->name_len);
|
||||
this_sf->name[this_sf->name_len] = '\0';
|
||||
|
||||
/* cache file_size */
|
||||
if (infile) {
|
||||
fseek_v(this_sf->infile, 0x00, SEEK_END);
|
||||
this_sf->file_size = ftell_v(this_sf->infile);
|
||||
fseek_v(this_sf->infile, 0x00, SEEK_SET);
|
||||
}
|
||||
else {
|
||||
this_sf->file_size = 0; /* allow virtual, non-existing files */
|
||||
}
|
||||
|
||||
/* Typically fseek(o)/ftell(o) may only handle up to ~2.14GB, signed 32b = 0x7FFFFFFF (rarely
|
||||
* happens in giant banks like FSB/KTSR). Should work if configured properly using ftell_v, log otherwise. */
|
||||
if (this_sf->file_size == 0xFFFFFFFF) { /* -1 on error */
|
||||
vgm_logi("STREAMFILE: file size too big (report)\n");
|
||||
goto fail; /* can be ignored but may result in strange/unexpected behaviors */
|
||||
}
|
||||
|
||||
/* Rarely a TXTP needs to open *many* streamfiles = many file descriptors = reaches OS limit = error.
|
||||
* Ideally should detect better and open/close as needed or reuse FDs for files that don't play at
|
||||
* the same time, but it's complex since every SF is separate (would need some kind of FD manager).
|
||||
* For the time being, if the file is smaller that buffer we can just read it fully and close the FD,
|
||||
* that should help since big TXTP usually just need many small files.
|
||||
* Doubles as an optimization as most files given will be read fully into buf on first read. */
|
||||
if (this_sf->file_size && this_sf->file_size < this_sf->buf_size && this_sf->infile) {
|
||||
//;VGM_LOG("stdio: fit filesize %x into buf %x\n", sf->file_size, sf->buf_size);
|
||||
|
||||
this_sf->buf_offset = 0;
|
||||
this_sf->valid_size = fread(this_sf->buf, sizeof(uint8_t), this_sf->file_size, this_sf->infile);
|
||||
|
||||
fclose(this_sf->infile);
|
||||
this_sf->infile = NULL;
|
||||
}
|
||||
|
||||
return &this_sf->vt;
|
||||
|
||||
fail:
|
||||
free(buf);
|
||||
free(this_sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static STREAMFILE* open_stdio_streamfile_buffer(const char* const filename, size_t bufsize) {
|
||||
FILE* infile = NULL;
|
||||
STREAMFILE* sf = NULL;
|
||||
|
||||
infile = fopen_v(filename,"rb");
|
||||
if (!infile) {
|
||||
/* allow non-existing files in some cases */
|
||||
if (!vgmstream_is_virtual_filename(filename))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sf = open_stdio_streamfile_buffer_by_file(infile, filename, bufsize);
|
||||
if (!sf) {
|
||||
if (infile) fclose(infile);
|
||||
}
|
||||
|
||||
return sf;
|
||||
}
|
||||
|
||||
STREAMFILE* open_stdio_streamfile(const char* filename) {
|
||||
return open_stdio_streamfile_buffer(filename, 0);
|
||||
}
|
||||
|
||||
STREAMFILE* open_stdio_streamfile_by_file(FILE* file, const char* filename) {
|
||||
return open_stdio_streamfile_buffer_by_file(file, filename, 0);
|
||||
}
|
||||
|
||||
|
||||
/* ************************************************************************* */
|
||||
|
||||
void dump_streamfile(STREAMFILE* sf, int num) {
|
||||
#ifdef VGM_DEBUG_OUTPUT
|
||||
offv_t offset = 0;
|
||||
FILE* f = NULL;
|
||||
|
||||
if (num >= 0) {
|
||||
char filename[PATH_LIMIT];
|
||||
char dumpname[PATH_LIMIT];
|
||||
|
||||
get_streamfile_filename(sf, filename, sizeof(filename));
|
||||
snprintf(dumpname, sizeof(dumpname), "%s_%02i.dump", filename, num);
|
||||
|
||||
f = fopen_v(dumpname,"wb");
|
||||
if (!f) return;
|
||||
}
|
||||
|
||||
VGM_LOG("dump streamfile %i: size %x\n", num, get_streamfile_size(sf));
|
||||
while (offset < get_streamfile_size(sf)) {
|
||||
uint8_t buf[0x8000];
|
||||
size_t bytes;
|
||||
|
||||
bytes = read_streamfile(buf, offset, sizeof(buf), sf);
|
||||
if(!bytes) {
|
||||
VGM_LOG("dump streamfile: can't read at %x\n", (uint32_t)offset);
|
||||
break;
|
||||
}
|
||||
|
||||
if (f)
|
||||
fwrite(buf, sizeof(uint8_t), bytes, f);
|
||||
else if (num == -1)
|
||||
VGM_LOGB(buf, bytes, 0);
|
||||
//else: don't do anything (read test)
|
||||
offset += bytes;
|
||||
}
|
||||
|
||||
if (f) {
|
||||
fclose(f);
|
||||
}
|
||||
#endif
|
||||
}
|
61
src/base/streamfile_wrap.c
Normal file
61
src/base/streamfile_wrap.c
Normal file
@ -0,0 +1,61 @@
|
||||
#include "../streamfile.h"
|
||||
|
||||
//todo stream_index: copy? pass? funtion? external?
|
||||
//todo use realnames on reopen? simplify?
|
||||
//todo use safe string ops, this ain't easy
|
||||
|
||||
typedef struct {
|
||||
STREAMFILE vt;
|
||||
|
||||
STREAMFILE* inner_sf;
|
||||
} WRAP_STREAMFILE;
|
||||
|
||||
static size_t wrap_read(WRAP_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
|
||||
return sf->inner_sf->read(sf->inner_sf, dst, offset, length); /* default */
|
||||
}
|
||||
static size_t wrap_get_size(WRAP_STREAMFILE* sf) {
|
||||
return sf->inner_sf->get_size(sf->inner_sf); /* default */
|
||||
}
|
||||
static offv_t wrap_get_offset(WRAP_STREAMFILE* sf) {
|
||||
return sf->inner_sf->get_offset(sf->inner_sf); /* default */
|
||||
}
|
||||
static void wrap_get_name(WRAP_STREAMFILE* sf, char* name, size_t name_size) {
|
||||
sf->inner_sf->get_name(sf->inner_sf, name, name_size); /* default */
|
||||
}
|
||||
|
||||
static STREAMFILE* wrap_open(WRAP_STREAMFILE* sf, const char* const filename, size_t buf_size) {
|
||||
return sf->inner_sf->open(sf->inner_sf, filename, buf_size); /* default (don't call open_wrap_streamfile) */
|
||||
}
|
||||
|
||||
static void wrap_close(WRAP_STREAMFILE* sf) {
|
||||
//sf->inner_sf->close(sf->inner_sf); /* don't close */
|
||||
free(sf);
|
||||
}
|
||||
|
||||
STREAMFILE* open_wrap_streamfile(STREAMFILE* sf) {
|
||||
WRAP_STREAMFILE* this_sf = NULL;
|
||||
|
||||
if (!sf) return NULL;
|
||||
|
||||
this_sf = calloc(1, sizeof(WRAP_STREAMFILE));
|
||||
if (!this_sf) return NULL;
|
||||
|
||||
/* set callbacks and internals */
|
||||
this_sf->vt.read = (void*)wrap_read;
|
||||
this_sf->vt.get_size = (void*)wrap_get_size;
|
||||
this_sf->vt.get_offset = (void*)wrap_get_offset;
|
||||
this_sf->vt.get_name = (void*)wrap_get_name;
|
||||
this_sf->vt.open = (void*)wrap_open;
|
||||
this_sf->vt.close = (void*)wrap_close;
|
||||
this_sf->vt.stream_index = sf->stream_index;
|
||||
|
||||
this_sf->inner_sf = sf;
|
||||
|
||||
return &this_sf->vt;
|
||||
}
|
||||
STREAMFILE* open_wrap_streamfile_f(STREAMFILE* sf) {
|
||||
STREAMFILE* new_sf = open_wrap_streamfile(sf);
|
||||
if (!new_sf)
|
||||
close_streamfile(sf);
|
||||
return new_sf;
|
||||
}
|
268
src/base/tags.c
Normal file
268
src/base/tags.c
Normal file
@ -0,0 +1,268 @@
|
||||
#include "../vgmstream.h"
|
||||
#include "../util/log.h"
|
||||
#include "../util/reader_sf.h"
|
||||
#include "../util/reader_text.h"
|
||||
#include "plugins.h"
|
||||
|
||||
/* TAGS: loads key=val tags from a file */
|
||||
|
||||
#define VGMSTREAM_TAGS_LINE_MAX 2048
|
||||
|
||||
/* opaque tag state */
|
||||
struct VGMSTREAM_TAGS {
|
||||
/* extracted output */
|
||||
char key[VGMSTREAM_TAGS_LINE_MAX];
|
||||
char val[VGMSTREAM_TAGS_LINE_MAX];
|
||||
|
||||
/* file to find tags for */
|
||||
int targetname_len;
|
||||
char targetname[VGMSTREAM_TAGS_LINE_MAX];
|
||||
/* path of targetname */
|
||||
char targetpath[VGMSTREAM_TAGS_LINE_MAX];
|
||||
|
||||
/* tag section for filename (see comments below) */
|
||||
int section_found;
|
||||
off_t section_start;
|
||||
off_t section_end;
|
||||
off_t offset;
|
||||
|
||||
/* commands */
|
||||
int autotrack_on;
|
||||
int autotrack_written;
|
||||
int track_count;
|
||||
int exact_match;
|
||||
|
||||
int autoalbum_on;
|
||||
int autoalbum_written;
|
||||
};
|
||||
|
||||
|
||||
static void tags_clean(VGMSTREAM_TAGS* tag) {
|
||||
int i;
|
||||
int val_len = strlen(tag->val);
|
||||
|
||||
/* remove trailing spaces */
|
||||
for (i = val_len - 1; i > 0; i--) {
|
||||
if (tag->val[i] != ' ')
|
||||
break;
|
||||
tag->val[i] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val) {
|
||||
VGMSTREAM_TAGS* tags = malloc(sizeof(VGMSTREAM_TAGS));
|
||||
if (!tags) goto fail;
|
||||
|
||||
*tag_key = tags->key;
|
||||
*tag_val = tags->val;
|
||||
|
||||
return tags;
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void vgmstream_tags_close(VGMSTREAM_TAGS *tags) {
|
||||
free(tags);
|
||||
}
|
||||
|
||||
/* Find next tag and return 1 if found.
|
||||
*
|
||||
* Tags can be "global" @TAGS, "command" $TAGS, and "file" %TAGS for a target filename.
|
||||
* To extract tags we must find either global tags, or the filename's tag "section"
|
||||
* where tags apply: (# @TAGS ) .. (other_filename) ..(# %TAGS section).. (target_filename).
|
||||
* When a new "other_filename" is found that offset is marked as section_start, and when
|
||||
* target_filename is found it's marked as section_end. Then we can begin extracting tags
|
||||
* within that section, until all tags are exhausted. Global tags are extracted as found,
|
||||
* so they always go first, also meaning any tags after file's section are ignored.
|
||||
* Command tags have special meanings and are output after all section tags. */
|
||||
int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
|
||||
off_t file_size = get_streamfile_size(tagfile);
|
||||
char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0};
|
||||
char line[VGMSTREAM_TAGS_LINE_MAX];
|
||||
int ok, bytes_read, line_ok, n1,n2;
|
||||
|
||||
if (!tags)
|
||||
return 0;
|
||||
|
||||
/* prepare file start and skip BOM if needed */
|
||||
if (tags->offset == 0) {
|
||||
size_t bom_size = read_bom(tagfile);
|
||||
tags->offset = bom_size;
|
||||
if (tags->section_start == 0)
|
||||
tags->section_start = bom_size;
|
||||
}
|
||||
|
||||
/* read lines */
|
||||
while (tags->offset <= file_size) {
|
||||
|
||||
/* after section: no more tags to extract */
|
||||
if (tags->section_found && tags->offset >= tags->section_end) {
|
||||
|
||||
/* write extra tags after all regular tags */
|
||||
if (tags->autotrack_on && !tags->autotrack_written) {
|
||||
sprintf(tags->key, "%s", "TRACK");
|
||||
sprintf(tags->val, "%i", tags->track_count);
|
||||
tags->autotrack_written = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (tags->autoalbum_on && !tags->autoalbum_written && tags->targetpath[0] != '\0') {
|
||||
const char* path;
|
||||
|
||||
path = strrchr(tags->targetpath,'\\');
|
||||
if (!path) {
|
||||
path = strrchr(tags->targetpath,'/');
|
||||
}
|
||||
if (!path) {
|
||||
path = tags->targetpath;
|
||||
}
|
||||
|
||||
sprintf(tags->key, "%s", "ALBUM");
|
||||
sprintf(tags->val, "%s", path+1);
|
||||
tags->autoalbum_written = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
bytes_read = read_line(line, sizeof(line), tags->offset, tagfile, &line_ok);
|
||||
if (!line_ok || bytes_read == 0) goto fail;
|
||||
|
||||
tags->offset += bytes_read;
|
||||
|
||||
|
||||
if (tags->section_found) {
|
||||
/* find possible file tag */
|
||||
ok = sscanf(line, "# %%%[^%%]%% %[^\r\n] ", tags->key,tags->val); /* key with spaces */
|
||||
if (ok != 2)
|
||||
ok = sscanf(line, "# %%%[^ \t] %[^\r\n] ", tags->key,tags->val); /* key without */
|
||||
if (ok == 2) {
|
||||
tags_clean(tags);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
if (line[0] == '#') {
|
||||
/* find possible global command */
|
||||
ok = sscanf(line, "# $%n%[^ \t]%n %[^\r\n]", &n1, tags->key, &n2, tags->val);
|
||||
if (ok == 1 || ok == 2) {
|
||||
int key_len = n2 - n1;
|
||||
if (strncasecmp(tags->key, "AUTOTRACK", key_len) == 0) {
|
||||
tags->autotrack_on = 1;
|
||||
}
|
||||
else if (strncasecmp(tags->key, "AUTOALBUM", key_len) == 0) {
|
||||
tags->autoalbum_on = 1;
|
||||
}
|
||||
else if (strncasecmp(tags->key, "EXACTMATCH", key_len) == 0) {
|
||||
tags->exact_match = 1;
|
||||
}
|
||||
|
||||
continue; /* not an actual tag */
|
||||
}
|
||||
|
||||
/* find possible global tag */
|
||||
ok = sscanf(line, "# @%[^@]@ %[^\r\n]", tags->key, tags->val); /* key with spaces */
|
||||
if (ok != 2)
|
||||
ok = sscanf(line, "# @%[^ \t] %[^\r\n]", tags->key, tags->val); /* key without */
|
||||
if (ok == 2) {
|
||||
tags_clean(tags);
|
||||
return 1;
|
||||
}
|
||||
|
||||
continue; /* next line */
|
||||
}
|
||||
|
||||
/* find possible filename and section start/end
|
||||
* (.m3u seem to allow filenames with whitespaces before, make sure to trim) */
|
||||
ok = sscanf(line, " %n%[^\r\n]%n ", &n1, currentname, &n2);
|
||||
if (ok == 1) {
|
||||
int currentname_len = n2 - n1;
|
||||
int filename_found = 0;
|
||||
|
||||
/* we want to match file with the same name (case insensitive), OR a virtual .txtp with
|
||||
* the filename inside to ease creation of tag files with config, also check end char to
|
||||
* tell apart the unlikely case of having both 'bgm01.ad.txtp' and 'bgm01.adp.txtp' */
|
||||
|
||||
/* try exact match (strcasecmp works ok even for UTF-8) */
|
||||
if (currentname_len == tags->targetname_len &&
|
||||
strncasecmp(currentname, tags->targetname, currentname_len) == 0) {
|
||||
filename_found = 1;
|
||||
}
|
||||
else if (!tags->exact_match) {
|
||||
/* try tagfile is "bgm.adx" + target is "bgm.adx #(cfg) .txtp" */
|
||||
if (currentname_len < tags->targetname_len &&
|
||||
strncasecmp(currentname, tags->targetname, currentname_len) == 0 &&
|
||||
vgmstream_is_virtual_filename(tags->targetname)) {
|
||||
char c = tags->targetname[currentname_len];
|
||||
filename_found = (c==' ' || c == '.' || c == '#');
|
||||
}
|
||||
/* tagfile has "bgm.adx (...) .txtp" + target has "bgm.adx" */
|
||||
else if (tags->targetname_len < currentname_len &&
|
||||
strncasecmp(tags->targetname, currentname, tags->targetname_len) == 0 &&
|
||||
vgmstream_is_virtual_filename(currentname)) {
|
||||
char c = currentname[tags->targetname_len];
|
||||
filename_found = (c==' ' || c == '.' || c == '#');
|
||||
}
|
||||
}
|
||||
|
||||
if (filename_found) {
|
||||
/* section ok, start would be set before this (or be 0) */
|
||||
tags->section_end = tags->offset;
|
||||
tags->section_found = 1;
|
||||
tags->offset = tags->section_start;
|
||||
}
|
||||
else {
|
||||
/* mark new possible section */
|
||||
tags->section_start = tags->offset;
|
||||
}
|
||||
|
||||
tags->track_count++; /* new track found (target filename or not) */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* empty/bad line, probably */
|
||||
}
|
||||
}
|
||||
|
||||
/* may reach here if read up to file_size but no section was found */
|
||||
|
||||
fail:
|
||||
tags->key[0] = '\0';
|
||||
tags->val[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) {
|
||||
char *path;
|
||||
|
||||
if (!tags)
|
||||
return;
|
||||
|
||||
memset(tags, 0, sizeof(VGMSTREAM_TAGS));
|
||||
|
||||
//todo validate sizes and copy sensible max
|
||||
|
||||
/* get base name */
|
||||
strcpy(tags->targetpath, target_filename);
|
||||
|
||||
/* Windows CMD accepts both \\ and /, and maybe plugin uses either */
|
||||
path = strrchr(tags->targetpath,'\\');
|
||||
if (!path) {
|
||||
path = strrchr(tags->targetpath,'/');
|
||||
}
|
||||
if (path != NULL) {
|
||||
path[0] = '\0'; /* leave targetpath with path only */
|
||||
path = path+1;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
strcpy(tags->targetname, path);
|
||||
} else {
|
||||
tags->targetpath[0] = '\0';
|
||||
strcpy(tags->targetname, target_filename);
|
||||
}
|
||||
tags->targetname_len = strlen(tags->targetname);
|
||||
}
|
@ -273,7 +273,7 @@ static size_t opus_io_size(STREAMFILE* sf, opus_io_data* data) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (data_size == 0) {
|
||||
if (data_size <= 0 || data_size >= 0xFFFFF) { /* arbitrary max + catch -1/EOF */
|
||||
VGM_LOG("OPUS: data_size is 0 at %x\n", (uint32_t)offset);
|
||||
return 0; /* bad rip? or could 'break' and truck along */
|
||||
}
|
||||
@ -642,7 +642,7 @@ static size_t get_table_frame_size(opus_io_data* data, int frame) {
|
||||
static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE* sf, opus_type_t type) {
|
||||
size_t num_samples = 0;
|
||||
off_t end_offset = offset + stream_size;
|
||||
int packet = 0;
|
||||
//int packet = 0;
|
||||
|
||||
if (end_offset > get_streamfile_size(sf)) {
|
||||
VGM_LOG("OPUS: wrong end offset found\n");
|
||||
@ -699,7 +699,7 @@ static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFI
|
||||
num_samples += packet_samples;
|
||||
|
||||
offset += skip_size + data_size;
|
||||
packet++;
|
||||
//packet++;
|
||||
}
|
||||
|
||||
return num_samples;
|
||||
|
@ -4,26 +4,26 @@
|
||||
|
||||
/* Must pass at least 8 bytes of data to this function.
|
||||
* Returns <0 on non-match, or header size on success. */
|
||||
int clHCA_isOurFile(const void *data, unsigned int size);
|
||||
int clHCA_isOurFile(const void* data, unsigned int size);
|
||||
|
||||
/* The opaque state structure. */
|
||||
typedef struct clHCA clHCA;
|
||||
|
||||
/* In case you wish to allocate and reset the structure on your own. */
|
||||
int clHCA_sizeof(void);
|
||||
void clHCA_clear(clHCA *);
|
||||
void clHCA_done(clHCA *);
|
||||
void clHCA_clear(clHCA* hca);
|
||||
void clHCA_done(clHCA* hca);
|
||||
|
||||
/* Or you could let the library allocate it. */
|
||||
clHCA * clHCA_new(void);
|
||||
void clHCA_delete(clHCA *);
|
||||
clHCA* clHCA_new(void);
|
||||
void clHCA_delete(clHCA* hca);
|
||||
|
||||
/* Parses the HCA header. Must be called before any decoding may be performed,
|
||||
* and size must be at least headerSize long. The recommended way is to detect
|
||||
* the header length with clHCA_isOurFile, then read data and call this.
|
||||
* May be called multiple times to reset decoder state.
|
||||
* Returns 0 on success, <0 on failure. */
|
||||
int clHCA_DecodeHeader(clHCA *, const void *data, unsigned int size);
|
||||
int clHCA_DecodeHeader(clHCA* hca, const void* data, unsigned int size);
|
||||
|
||||
typedef struct clHCA_stInfo {
|
||||
unsigned int version;
|
||||
@ -40,7 +40,7 @@ typedef struct clHCA_stInfo {
|
||||
unsigned int loopStartDelay; /* samples in block before loop starts */
|
||||
unsigned int loopEndPadding; /* samples in block after loop ends */
|
||||
unsigned int samplesPerBlock; /* should be 1024 */
|
||||
const char *comment;
|
||||
const char* comment;
|
||||
unsigned int encryptionEnabled; /* requires keycode */
|
||||
|
||||
/* Derived sample formulas:
|
||||
@ -53,33 +53,33 @@ typedef struct clHCA_stInfo {
|
||||
/* Retrieves header information for decoding and playback (it's the caller's responsability
|
||||
* to apply looping, encoder delay/skip samples, etc). May be called after clHCA_DecodeHeader.
|
||||
* Returns 0 on success, <0 on failure. */
|
||||
int clHCA_getInfo(clHCA *, clHCA_stInfo *out);
|
||||
int clHCA_getInfo(clHCA* hca, clHCA_stInfo* out);
|
||||
|
||||
/* Decodes a single frame, from data after headerSize. Should be called after
|
||||
* clHCA_DecodeHeader and size must be at least blockSize long.
|
||||
* Data may be modified if encrypted.
|
||||
* Returns 0 on success, <0 on failure. */
|
||||
int clHCA_DecodeBlock(clHCA *, void *data, unsigned int size);
|
||||
int clHCA_DecodeBlock(clHCA* hca, void* data, unsigned int size);
|
||||
|
||||
/* Extracts signed and clipped 16 bit samples into sample buffer.
|
||||
* May be called after clHCA_DecodeBlock, and will return the same data until
|
||||
* next decode. Buffer must be at least (samplesPerBlock*channels) long. */
|
||||
void clHCA_ReadSamples16(clHCA *, signed short * outSamples);
|
||||
void clHCA_ReadSamples16(clHCA* hca, short* outSamples);
|
||||
|
||||
/* Sets a 64 bit encryption key, to properly decode blocks. This may be called
|
||||
* multiple times to change the key, before or after clHCA_DecodeHeader.
|
||||
* Key is ignored if the file is not encrypted. */
|
||||
void clHCA_SetKey(clHCA *, unsigned long long keycode);
|
||||
void clHCA_SetKey(clHCA* hca, unsigned long long keycode);
|
||||
|
||||
/* Tests a single frame for validity, mainly to test if current key is correct.
|
||||
* Returns <0 on incorrect block (wrong key), 0 on silent block (not useful to determine)
|
||||
* and >0 if block is correct (the closer to 1 the more likely).
|
||||
* Incorrect keys may give a few valid frames, so it's best to test a number of them
|
||||
* and select the key with scores closer to 1. */
|
||||
int clHCA_TestBlock(clHCA *hca, void *data, unsigned int size);
|
||||
int clHCA_TestBlock(clHCA* hca, void* data, unsigned int size);
|
||||
|
||||
/* Resets the internal decode state, used when restarting to decode the file from the beginning.
|
||||
* Without it there are minor differences, mainly useful when testing a new key. */
|
||||
void clHCA_DecodeReset(clHCA * hca);
|
||||
void clHCA_DecodeReset(clHCA* hca);
|
||||
|
||||
#endif
|
||||
|
@ -734,12 +734,18 @@ static const char* common_extension_list[] = {
|
||||
|
||||
|
||||
/* List supported formats and return elements in the list, for plugins that need to know. */
|
||||
const char ** vgmstream_get_formats(size_t * size) {
|
||||
const char** vgmstream_get_formats(size_t* size) {
|
||||
if (!size)
|
||||
return NULL;
|
||||
|
||||
*size = sizeof(extension_list) / sizeof(char*);
|
||||
return extension_list;
|
||||
}
|
||||
|
||||
const char ** vgmstream_get_common_formats(size_t * size) {
|
||||
const char** vgmstream_get_common_formats(size_t* size) {
|
||||
if (!size)
|
||||
return NULL;
|
||||
|
||||
*size = sizeof(common_extension_list) / sizeof(char*);
|
||||
return common_extension_list;
|
||||
}
|
||||
@ -766,18 +772,18 @@ typedef struct {
|
||||
static const coding_info coding_info_list[] = {
|
||||
{coding_SILENCE, "Silence"},
|
||||
|
||||
{coding_PCM16LE, "Little Endian 16-bit PCM"},
|
||||
{coding_PCM16BE, "Big Endian 16-bit PCM"},
|
||||
{coding_PCM16_int, "16-bit PCM with 2 byte interleave (block)"},
|
||||
{coding_PCM16LE, "16-bit Little Endian PCM"},
|
||||
{coding_PCM16BE, "16-bit Big Endian PCM"},
|
||||
{coding_PCM16_int, "16-bit PCM (block)"},
|
||||
{coding_PCM8, "8-bit signed PCM"},
|
||||
{coding_PCM8_int, "8-bit signed PCM with 1 byte interleave (block)"},
|
||||
{coding_PCM8_int, "8-bit signed PCM (block)"},
|
||||
{coding_PCM8_U, "8-bit unsigned PCM"},
|
||||
{coding_PCM8_U_int, "8-bit unsigned PCM with 1 byte interleave (block)"},
|
||||
{coding_PCM8_SB, "8-bit PCM with sign bit"},
|
||||
{coding_PCM8_U_int, "8-bit unsigned PCM (block)"},
|
||||
{coding_PCM8_SB, "8-bit sign bit PCM"},
|
||||
{coding_PCM4, "4-bit signed PCM"},
|
||||
{coding_PCM4_U, "4-bit unsigned PCM"},
|
||||
{coding_ULAW, "8-bit u-Law"},
|
||||
{coding_ULAW_int, "8-bit u-Law with 1 byte interleave (block)"},
|
||||
{coding_ULAW_int, "8-bit u-Law (block)"},
|
||||
{coding_ALAW, "8-bit a-Law"},
|
||||
{coding_PCMFLOAT, "32-bit float PCM"},
|
||||
{coding_PCM24LE, "24-bit Little Endian PCM"},
|
||||
@ -876,9 +882,9 @@ static const coding_info coding_info_list[] = {
|
||||
{coding_COMPRESSWAVE, "CompressWave Huffman ADPCM"},
|
||||
|
||||
{coding_SDX2, "Squareroot-delta-exact (SDX2) 8-bit DPCM"},
|
||||
{coding_SDX2_int, "Squareroot-delta-exact (SDX2) 8-bit DPCM with 1 byte interleave"},
|
||||
{coding_SDX2_int, "Squareroot-delta-exact (SDX2) 8-bit DPCM (block)"},
|
||||
{coding_CBD2, "Cuberoot-delta-exact (CBD2) 8-bit DPCM"},
|
||||
{coding_CBD2_int, "Cuberoot-delta-exact (CBD2) 8-bit DPCM with 1 byte interleave"},
|
||||
{coding_CBD2_int, "Cuberoot-delta-exact (CBD2) 8-bit DPCM (block)"},
|
||||
{coding_SASSC, "Activision / EXAKT SASSC 8-bit DPCM"},
|
||||
{coding_DERF, "Xilam DERF 8-bit DPCM"},
|
||||
{coding_WADY, "Marble WADY 8-bit DPCM"},
|
||||
@ -1010,7 +1016,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_XA, "Sony XA header"},
|
||||
{meta_RXWS, "Sony RXWS header"},
|
||||
{meta_RAW_INT, "PS2 .int raw header"},
|
||||
{meta_OMU, "Outrage OMU Header"},
|
||||
{meta_OMU, "Outrage OMU header"},
|
||||
{meta_DSP_STM, "Intelligent Systems STM header"},
|
||||
{meta_EXST, "Sony EXST header"},
|
||||
{meta_SVAG_KCET, "Konami SVAG header"},
|
||||
@ -1035,8 +1041,8 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_DSP_STR, "Cauldron .STR header"},
|
||||
{meta_EA_SCHL, "Electronic Arts SCHl header"},
|
||||
{meta_EA_SCHL_fixed, "Electronic Arts SCHl header (fixed)"},
|
||||
{meta_CAF, "tri-Crescendo CAF Header"},
|
||||
{meta_VPK, "SCE America VPK Header"},
|
||||
{meta_CAF, "tri-Crescendo CAF header"},
|
||||
{meta_VPK, "SCE America VPK header"},
|
||||
{meta_GENH, "GENH generic header"},
|
||||
{meta_DSP_SADB, "Procyon Studio SADB header"},
|
||||
{meta_SADL, "Procyon Studio SADL header"},
|
||||
@ -1055,7 +1061,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_NWA_GAMEEXEINI, "VisualArt's NWA header (Gameexe.ini looping)"},
|
||||
{meta_XSS, "Dino Crisis 3 XSS File"},
|
||||
{meta_HGC1, "Cauldron HGC1 header"},
|
||||
{meta_AUS, "Capcom AUS Header"},
|
||||
{meta_AUS, "Atomic Planet AUS header"},
|
||||
{meta_RWS, "RenderWare RWS header"},
|
||||
{meta_EA_1SNH, "Electronic Arts 1SNh header"},
|
||||
{meta_EA_EACS, "Electronic Arts EACS header"},
|
||||
@ -1070,17 +1076,17 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_PS2_XA30, "Reflections XA30 PS2 header"},
|
||||
{meta_MUSC, "Krome MUSC header"},
|
||||
{meta_MUSX, "Eurocom MUSX header"},
|
||||
{meta_FILP, "cavia FILp Header"},
|
||||
{meta_FILP, "cavia FILp header"},
|
||||
{meta_IKM, "MiCROViSiON IKM header"},
|
||||
{meta_STER, "ALCHEMY STER header"},
|
||||
{meta_SAT_DVI, "Konami DVI. header"},
|
||||
{meta_DC_KCEY, "Konami KCEY header"},
|
||||
{meta_BG00, "Cave BG00 header"},
|
||||
{meta_RSTM_ROCKSTAR, "Rockstar Games RSTM Header"},
|
||||
{meta_ACM, "InterPlay ACM Header"},
|
||||
{meta_RSTM_ROCKSTAR, "Rockstar Games RSTM header"},
|
||||
{meta_ACM, "InterPlay ACM header"},
|
||||
{meta_MUS_ACM, "InterPlay MUS ACM header"},
|
||||
{meta_VIG_KCES, "Konami .VIG Header"},
|
||||
{meta_HXD, "Tecmo HXD Header"},
|
||||
{meta_VIG_KCES, "Konami .VIG header"},
|
||||
{meta_HXD, "Tecmo HXD header"},
|
||||
{meta_VSV, "Square Enix .vsv Header"},
|
||||
{meta_RIFF_WAVE_labl, "RIFF WAVE header (labl looping)"},
|
||||
{meta_RIFF_WAVE_smpl, "RIFF WAVE header (smpl looping)"},
|
||||
@ -1101,7 +1107,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_STR_SEGA_custom, "Sega Stream Asset Builder header (custom)"},
|
||||
{meta_XMU, "Outrage XMU header"},
|
||||
{meta_XVAS, "Konami .XVAS header"},
|
||||
{meta_XA2_ACCLAIM, "Acclaim .XA2 Header"},
|
||||
{meta_XA2_ACCLAIM, "Acclaim .XA2 header"},
|
||||
{meta_SAP, "VING .SAP header"},
|
||||
{meta_DC_IDVI, "Capcom IDVI header"},
|
||||
{meta_KRAW, "Geometry Wars: Galaxies KRAW header"},
|
||||
|
@ -2,20 +2,31 @@
|
||||
#include "../vgmstream.h"
|
||||
|
||||
/* each block is a new CAF header */
|
||||
void block_update_caf(off_t block_offset, VGMSTREAM * vgmstream) {
|
||||
STREAMFILE* streamFile = vgmstream->ch[0].streamfile;
|
||||
int i,ch;
|
||||
void block_update_caf(off_t block_offset, VGMSTREAM* vgmstream) {
|
||||
STREAMFILE* sf = vgmstream->ch[0].streamfile;
|
||||
|
||||
// 00: "CAF "
|
||||
// 04: block size
|
||||
// 08: block number
|
||||
// 0c: empty
|
||||
// 10: channel 1 offset
|
||||
// 14: channel 1 size
|
||||
// 18: channel 2 offset
|
||||
// 1c: channel 2 size
|
||||
// 20: loop start
|
||||
// 24: loop end (same as last block)
|
||||
// 28: DSP header stuff (repeated per block)
|
||||
|
||||
vgmstream->current_block_offset = block_offset;
|
||||
vgmstream->next_block_offset = block_offset + read_32bitBE(block_offset+0x04, streamFile);
|
||||
vgmstream->current_block_size = read_32bitBE(block_offset+0x14, streamFile);
|
||||
vgmstream->next_block_offset = block_offset + read_u32be(block_offset + 0x04, sf);
|
||||
vgmstream->current_block_size = read_u32be(block_offset + 0x14, sf);
|
||||
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
vgmstream->ch[ch].offset = block_offset + read_32bitBE(block_offset+0x10+(0x08*ch), streamFile);
|
||||
for (int ch = 0; ch < vgmstream->channels; ch++) {
|
||||
vgmstream->ch[ch].offset = block_offset + read_u32be(block_offset + 0x10 + 0x08 * ch, sf);
|
||||
|
||||
/* re-read coeffs (though blocks seem to repeat them) */
|
||||
for (i = 0; i < 16; i++) {
|
||||
vgmstream->ch[ch].adpcm_coef[i] = read_16bitBE(block_offset+0x34 + 0x2c*ch + 0x02*i, streamFile);
|
||||
for (int i = 0; i < 16; i++) {
|
||||
vgmstream->ch[ch].adpcm_coef[i] = read_s16be(block_offset + 0x34 + 0x2c * ch + 0x02 * i, sf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,11 +81,16 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="api.h" />
|
||||
<ClInclude Include="api_decode.h" />
|
||||
<ClInclude Include="api_helpers.h" />
|
||||
<ClInclude Include="api_streamfile.h" />
|
||||
<ClInclude Include="api_tags.h" />
|
||||
<ClInclude Include="streamfile.h" />
|
||||
<ClInclude Include="streamtypes.h" />
|
||||
<ClInclude Include="util.h" />
|
||||
<ClInclude Include="vgmstream.h" />
|
||||
<ClInclude Include="vgmstream_types.h" />
|
||||
<ClInclude Include="base\api_internal.h" />
|
||||
<ClInclude Include="base\decode.h" />
|
||||
<ClInclude Include="base\mixing.h" />
|
||||
<ClInclude Include="base\mixing_fades.h" />
|
||||
@ -197,6 +202,7 @@
|
||||
<ClInclude Include="util\samples_ops.h" />
|
||||
<ClInclude Include="util\sf_utils.h" />
|
||||
<ClInclude Include="util\text_reader.h" />
|
||||
<ClInclude Include="util\vgmstream_limits.h" />
|
||||
<ClInclude Include="util\zlib_vgmstream.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@ -204,6 +210,13 @@
|
||||
<ClCompile Include="streamfile.c" />
|
||||
<ClCompile Include="util.c" />
|
||||
<ClCompile Include="vgmstream.c" />
|
||||
<ClCompile Include="base\api_decode_base.c" />
|
||||
<ClCompile Include="base\api_decode_open.c" />
|
||||
<ClCompile Include="base\api_decode_play.c" />
|
||||
<ClCompile Include="base\api_helpers.c" />
|
||||
<ClCompile Include="base\api_libsf.c" />
|
||||
<ClCompile Include="base\api_tags.c" />
|
||||
<ClCompile Include="base\config.c" />
|
||||
<ClCompile Include="base\decode.c" />
|
||||
<ClCompile Include="base\info.c" />
|
||||
<ClCompile Include="base\mixing.c" />
|
||||
@ -212,6 +225,15 @@
|
||||
<ClCompile Include="base\plugins.c" />
|
||||
<ClCompile Include="base\render.c" />
|
||||
<ClCompile Include="base\seek.c" />
|
||||
<ClCompile Include="base\streamfile_api.c" />
|
||||
<ClCompile Include="base\streamfile_buffer.c" />
|
||||
<ClCompile Include="base\streamfile_clamp.c" />
|
||||
<ClCompile Include="base\streamfile_fakename.c" />
|
||||
<ClCompile Include="base\streamfile_io.c" />
|
||||
<ClCompile Include="base\streamfile_multifile.c" />
|
||||
<ClCompile Include="base\streamfile_stdio.c" />
|
||||
<ClCompile Include="base\streamfile_wrap.c" />
|
||||
<ClCompile Include="base\tags.c" />
|
||||
<ClCompile Include="coding\acm_decoder.c" />
|
||||
<ClCompile Include="coding\adx_decoder.c" />
|
||||
<ClCompile Include="coding\asf_decoder.c" />
|
||||
|
@ -68,6 +68,18 @@
|
||||
<ClInclude Include="api.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="api_decode.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="api_helpers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="api_streamfile.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="api_tags.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="streamfile.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
@ -83,6 +95,9 @@
|
||||
<ClInclude Include="vgmstream_types.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="base\api_internal.h">
|
||||
<Filter>base\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="base\decode.h">
|
||||
<Filter>base\Header Files</Filter>
|
||||
</ClInclude>
|
||||
@ -416,6 +431,9 @@
|
||||
<ClInclude Include="util\text_reader.h">
|
||||
<Filter>util\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="util\vgmstream_limits.h">
|
||||
<Filter>util\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="util\zlib_vgmstream.h">
|
||||
<Filter>util\Header Files</Filter>
|
||||
</ClInclude>
|
||||
@ -433,6 +451,27 @@
|
||||
<ClCompile Include="vgmstream.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\api_decode_base.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\api_decode_open.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\api_decode_play.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\api_helpers.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\api_libsf.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\api_tags.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\config.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\decode.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -457,6 +496,33 @@
|
||||
<ClCompile Include="base\seek.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\streamfile_api.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\streamfile_buffer.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\streamfile_clamp.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\streamfile_fakename.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\streamfile_io.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\streamfile_multifile.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\streamfile_stdio.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\streamfile_wrap.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\tags.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="coding\acm_decoder.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
128
src/meta/caf.c
128
src/meta/caf.c
@ -1,62 +1,66 @@
|
||||
#include "meta.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "../util.h"
|
||||
|
||||
/* CAF - from tri-Crescendo games [Baten Kaitos 1/2 (GC), Fragile (Wii)] */
|
||||
VGMSTREAM * init_vgmstream_caf(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset, offset;
|
||||
size_t file_size;
|
||||
int channel_count, loop_flag;
|
||||
int32_t num_samples = 0;
|
||||
uint32_t loop_start = -1;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .caf: header id, .cfn: fake extension? , "" is accepted as files don't have extensions in the disc */
|
||||
if (!check_extensions(streamFile,"caf,cfn,"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x43414620) /* "CAF " */
|
||||
goto fail;
|
||||
|
||||
/* get total samples */
|
||||
offset = 0;
|
||||
file_size = get_streamfile_size(streamFile);
|
||||
while (offset < file_size) {
|
||||
off_t next_block = read_32bitBE(offset+0x04,streamFile);
|
||||
num_samples += read_32bitBE(offset+0x14,streamFile)/8*14;
|
||||
|
||||
if(read_32bitBE(offset+0x20,streamFile)==read_32bitBE(offset+0x08,streamFile)) {
|
||||
loop_start = num_samples - read_32bitBE(offset+0x14,streamFile)/8*14;
|
||||
}
|
||||
offset += next_block;
|
||||
}
|
||||
|
||||
start_offset = 0x00;
|
||||
channel_count = 2; /* always stereo */
|
||||
loop_flag = (loop_start!=-1);
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = 32000;
|
||||
vgmstream->num_samples = num_samples;
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = num_samples;
|
||||
}
|
||||
|
||||
vgmstream->meta_type = meta_CAF;
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_blocked_caf;
|
||||
|
||||
if ( !vgmstream_open_stream(vgmstream,streamFile,start_offset) )
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* CAF - from tri-Crescendo games [Baten Kaitos 1/2 (GC), Fragile (Wii)] */
|
||||
VGMSTREAM* init_vgmstream_caf(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int channels, loop_flag;
|
||||
int32_t num_samples = 0;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "CAF "))
|
||||
return NULL;
|
||||
|
||||
/* .caf: header id
|
||||
* (extensionless): files on disc don't have any extensions
|
||||
* .cfn: fake extension */
|
||||
if (!check_extensions(sf,"caf,cfn,"))
|
||||
return NULL;
|
||||
|
||||
/* get total samples from blocks + find loop */ //TODO reuse function calls
|
||||
uint32_t loop_start = -1;
|
||||
off_t offset = 0x00;
|
||||
off_t file_size = get_streamfile_size(sf);
|
||||
while (offset < file_size) {
|
||||
// see blocked layout for block info
|
||||
off_t next_block = read_u32be(offset+0x04,sf);
|
||||
off_t channel_bytes = read_u32be(offset+0x14,sf);
|
||||
int channel_samples = dsp_bytes_to_samples(channel_bytes, 1);
|
||||
|
||||
if (read_u32be(offset+0x08,sf) == read_u32be(offset+0x20,sf) && loop_start < 0) {
|
||||
loop_start = num_samples;
|
||||
}
|
||||
|
||||
num_samples += channel_samples;
|
||||
offset += next_block;
|
||||
}
|
||||
|
||||
start_offset = 0x00;
|
||||
channels = 2; /* always stereo */
|
||||
loop_flag = (loop_start != -1);
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = 32000;
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = num_samples;
|
||||
|
||||
vgmstream->meta_type = meta_CAF;
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_blocked_caf;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -1066,6 +1066,21 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_absolute(const char* fn) {
|
||||
return fn[0] == '/' || fn[0] == '\\' || fn[1] == ':';
|
||||
}
|
||||
|
||||
static STREAMFILE* open_path_streamfile(STREAMFILE* sf, char* path) {
|
||||
fix_dir_separators(path); /* clean paths */
|
||||
|
||||
/* absolute paths are detected for convenience, but since it's hard to unify all OSs
|
||||
* and plugins, they aren't "officially" supported nor documented, thus may or may not work */
|
||||
if (is_absolute(path))
|
||||
return open_streamfile(sf, path); /* from path as is */
|
||||
else
|
||||
return open_streamfile_by_filename(sf, path); /* from current path */
|
||||
}
|
||||
|
||||
static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, char* val) {
|
||||
|
||||
if (txth->debug)
|
||||
@ -1415,9 +1430,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
|
||||
txth->sf_head_opened = 1;
|
||||
}
|
||||
else { /* open file */
|
||||
fix_dir_separators(val); /* clean paths */
|
||||
|
||||
txth->sf_head = open_streamfile_by_filename(txth->sf, val);
|
||||
txth->sf_head = open_path_streamfile(txth->sf, val);
|
||||
if (!txth->sf_head) goto fail;
|
||||
txth->sf_head_opened = 1;
|
||||
}
|
||||
@ -1445,9 +1458,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char* key, cha
|
||||
txth->sf_body_opened = 1;
|
||||
}
|
||||
else { /* open file */
|
||||
fix_dir_separators(val); /* clean paths */
|
||||
|
||||
txth->sf_body = open_streamfile_by_filename(txth->sf, val);
|
||||
txth->sf_body = open_path_streamfile(txth->sf, val);
|
||||
if (!txth->sf_body) goto fail;
|
||||
txth->sf_body_opened = 1;
|
||||
}
|
||||
|
@ -2,49 +2,47 @@
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* VPK - from SCE America second party devs [God of War (PS2), NBA 08 (PS3)] */
|
||||
VGMSTREAM * init_vgmstream_vpk(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
int loop_flag, channel_count;
|
||||
VGMSTREAM* init_vgmstream_vpk(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
int loop_flag, channels;
|
||||
off_t start_offset, loop_channel_offset;
|
||||
size_t channel_size;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "vpk"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) != 0x204B5056) /* " KPV" */
|
||||
goto fail;
|
||||
if (!is_id32be(0x00,sf, " KPV"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf, "vpk"))
|
||||
return NULL;
|
||||
|
||||
/* files are padded with garbage/silent 0xC00000..00 frames, and channel_size sometimes
|
||||
* has extra size into the padding: +0x10 (NBA08), +0x20 (GoW), or none (Sly 2, loops ok).
|
||||
* Could detect and remove to slightly improve full loops, but maybe this is just how the game works */
|
||||
channel_size = read_32bitLE(0x04,streamFile);
|
||||
size_t channel_size = read_u32le(0x04,sf);
|
||||
|
||||
start_offset = read_32bitLE(0x08,streamFile);
|
||||
channel_count = read_32bitLE(0x14,streamFile);
|
||||
start_offset = read_u32le(0x08,sf);
|
||||
channels = read_s32le(0x14,sf);
|
||||
/* 0x18+: channel config(?), 0x04 per channel */
|
||||
loop_channel_offset = read_32bitLE(0x7FC,streamFile);
|
||||
loop_channel_offset = read_u32le(0x7FC,sf);
|
||||
loop_flag = (loop_channel_offset != 0); /* found in Sly 2/3 */
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = read_32bitLE(0x10,streamFile);
|
||||
vgmstream->num_samples = ps_bytes_to_samples(channel_size*vgmstream->channels,vgmstream->channels);
|
||||
vgmstream->sample_rate = read_s32le(0x10,sf);
|
||||
vgmstream->num_samples = ps_bytes_to_samples(channel_size * channels, channels);
|
||||
if (vgmstream->loop_flag) {
|
||||
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_channel_offset*vgmstream->channels,vgmstream->channels);
|
||||
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_channel_offset * channels, channels);
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
}
|
||||
|
||||
vgmstream->meta_type = meta_VPK;
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->interleave_block_size = read_32bitLE(0x0C,streamFile) / 2; /* even in >2ch */
|
||||
vgmstream->interleave_block_size = read_u32le(0x0C,sf) / 2; /* even in >2ch */
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
|
@ -75,7 +75,7 @@ typedef struct {
|
||||
int fix_xma_loop_samples;
|
||||
} xwb_header;
|
||||
|
||||
static void get_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf);
|
||||
static void get_name(char* buf, size_t buf_size, int target_subsong, xwb_header* xwb, STREAMFILE* sf);
|
||||
|
||||
|
||||
/* XWB - XACT Wave Bank (Microsoft SDK format for XBOX/XBOX360/Windows) */
|
||||
@ -638,10 +638,10 @@ fail:
|
||||
|
||||
/* ****************************************************************************** */
|
||||
|
||||
static int get_xwb_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf) {
|
||||
static int get_xwb_name(char* buf, size_t buf_size, int target_subsong, xwb_header* xwb, STREAMFILE* sf) {
|
||||
size_t read;
|
||||
|
||||
if (!xwb->names_offset || !xwb->names_size || xwb->names_entry_size > maxsize)
|
||||
if (!xwb->names_offset || !xwb->names_size || xwb->names_entry_size > buf_size)
|
||||
goto fail;
|
||||
|
||||
read = read_string(buf,xwb->names_entry_size, xwb->names_offset + xwb->names_entry_size*(target_subsong-1),sf);
|
||||
@ -653,7 +653,7 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_xsb_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf) {
|
||||
static int get_xsb_name(char* buf, size_t buf_size, int target_subsong, xwb_header* xwb, STREAMFILE* sf) {
|
||||
xsb_header xsb = {0};
|
||||
|
||||
xsb.selected_stream = target_subsong - 1;
|
||||
@ -670,8 +670,7 @@ static int get_xsb_name(char* buf, size_t maxsize, int target_subsong, xwb_heade
|
||||
if (!xsb.name_len || xsb.name[0] == '\0')
|
||||
goto fail;
|
||||
|
||||
strncpy(buf,xsb.name,maxsize);
|
||||
buf[maxsize-1] = '\0';
|
||||
snprintf(buf, buf_size, "%s", xsb.name);
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
@ -715,12 +714,12 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void get_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf_xwb) {
|
||||
static void get_name(char* buf, size_t buf_size, int target_subsong, xwb_header* xwb, STREAMFILE* sf_xwb) {
|
||||
STREAMFILE* sf_name = NULL;
|
||||
int name_found;
|
||||
|
||||
/* try to get the stream name in the .xwb, though they are very rarely included */
|
||||
name_found = get_xwb_name(buf, maxsize, target_subsong, xwb, sf_xwb);
|
||||
name_found = get_xwb_name(buf, buf_size, target_subsong, xwb, sf_xwb);
|
||||
if (name_found) return;
|
||||
|
||||
/* try again in a companion files */
|
||||
@ -730,7 +729,7 @@ static void get_name(char* buf, size_t maxsize, int target_subsong, xwb_header*
|
||||
sf_name = open_streamfile_by_ext(sf_xwb, "wbh");
|
||||
if (!sf_name) goto fail; /* rarely found [Pac-Man World 2 (Xbox)] */
|
||||
|
||||
name_found = get_wbh_name(buf, maxsize, target_subsong, xwb, sf_name);
|
||||
name_found = get_wbh_name(buf, buf_size, target_subsong, xwb, sf_name);
|
||||
close_streamfile(sf_name);
|
||||
}
|
||||
else {
|
||||
@ -738,7 +737,7 @@ static void get_name(char* buf, size_t maxsize, int target_subsong, xwb_header*
|
||||
sf_name = open_xsb_filename_pair(sf_xwb);
|
||||
if (!sf_name) goto fail; /* not all xwb have xsb though */
|
||||
|
||||
name_found = get_xsb_name(buf, maxsize, target_subsong, xwb, sf_name);
|
||||
name_found = get_xsb_name(buf, buf_size, target_subsong, xwb, sf_name);
|
||||
close_streamfile(sf_name);
|
||||
}
|
||||
|
||||
|
1024
src/streamfile.c
1024
src/streamfile.c
File diff suppressed because it is too large
Load Diff
@ -43,7 +43,7 @@ void vgm_log_set_callback(void* ctx_p, int level, int type, void* callback) {
|
||||
}
|
||||
|
||||
static void log_internal(void* ctx_p, int level, const char* fmt, va_list args) {
|
||||
char line[255];
|
||||
char line[256];
|
||||
int out;
|
||||
logger_t* ctx = ctx_p;
|
||||
if (!ctx) ctx = &log_impl;
|
||||
|
@ -1,43 +1,43 @@
|
||||
#ifndef _TEXT_READER_H_
|
||||
#define _TEXT_READER_H_
|
||||
|
||||
|
||||
/* Reader tuned for whole text files, reading chunks to minimize I/O with a single buffer.
|
||||
* For short lines read_line may be more appropriate (reads up to line end, while this reads bigger chunks),
|
||||
* which also allow \0 (this reader returns an error).
|
||||
* NOTE: modifies passed buffer (lines are forced to end with \0 rather than \n).
|
||||
*
|
||||
* Usage: set text_reader_t and defaults with text_reader_init, call text_reader_get_line(...) to get lines.
|
||||
* buf may be size+1 to allow 2^N chunk reads + trailing \0 (better performance?).
|
||||
*/
|
||||
|
||||
#include "../streamfile.h"
|
||||
|
||||
typedef struct {
|
||||
/* init */
|
||||
uint8_t* buf; /* where data will be read */
|
||||
int buf_size; /* size of the struct (also max line size) */
|
||||
STREAMFILE* sf; /* used to read data */
|
||||
uint32_t offset; /* sf pos */
|
||||
uint32_t max_offset; /* sf max */
|
||||
|
||||
/* internal */
|
||||
int filled; /* current buf bytes */
|
||||
int pos; /* current buf pos (last line) */
|
||||
int next_pos; /* buf pos on next call, after line end */
|
||||
int line_ok; /* current line is fully correct */
|
||||
|
||||
char* line;
|
||||
int line_len;
|
||||
} text_reader_t;
|
||||
|
||||
|
||||
/* convenience function to init the above struct */
|
||||
int text_reader_init(text_reader_t* tr, uint8_t* buf, int buf_size, STREAMFILE* sf, uint32_t offset, uint32_t max);
|
||||
|
||||
/* Reads and sets next line, or NULL if no lines are found (EOF).
|
||||
* returns line length (0 for empty lines), or <0 if line was too long to store in buf.
|
||||
* Will always return a valid (null terminated) string. */
|
||||
int text_reader_get_line(text_reader_t* tr, char** p_line);
|
||||
|
||||
#endif
|
||||
#ifndef _TEXT_READER_H_
|
||||
#define _TEXT_READER_H_
|
||||
|
||||
|
||||
/* Reader tuned for whole text files, reading chunks to minimize I/O with a single buffer.
|
||||
* For short lines read_line may be more appropriate (reads up to line end, while this reads bigger chunks),
|
||||
* which also allow \0 (this reader returns an error).
|
||||
* NOTE: modifies passed buffer (lines are forced to end with \0 rather than \n).
|
||||
*
|
||||
* Usage: set text_reader_t and defaults with text_reader_init, call text_reader_get_line(...) to get lines.
|
||||
* buf may be size+1 to allow 2^N chunk reads + trailing \0 (better performance?).
|
||||
*/
|
||||
|
||||
#include "../streamfile.h"
|
||||
|
||||
typedef struct {
|
||||
/* init */
|
||||
uint8_t* buf; /* where data will be read */
|
||||
int buf_size; /* size of the struct (also max line size) */
|
||||
STREAMFILE* sf; /* used to read data */
|
||||
uint32_t offset; /* sf pos */
|
||||
uint32_t max_offset; /* sf max */
|
||||
|
||||
/* internal */
|
||||
int filled; /* current buf bytes */
|
||||
int pos; /* current buf pos (last line) */
|
||||
int next_pos; /* buf pos on next call, after line end */
|
||||
int line_ok; /* current line is fully correct */
|
||||
|
||||
char* line;
|
||||
int line_len;
|
||||
} text_reader_t;
|
||||
|
||||
|
||||
/* convenience function to init the above struct */
|
||||
int text_reader_init(text_reader_t* tr, uint8_t* buf, int buf_size, STREAMFILE* sf, uint32_t offset, uint32_t max);
|
||||
|
||||
/* Reads and sets next line, or NULL if no lines are found (EOF).
|
||||
* returns line length (0 for empty lines), or <0 if line was too long to store in buf.
|
||||
* Will always return a valid (null terminated) string. */
|
||||
int text_reader_get_line(text_reader_t* tr, char** p_line);
|
||||
|
||||
#endif
|
||||
|
19
src/util/vgmstream_limits.h
Normal file
19
src/util/vgmstream_limits.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef _VGMSTREAM_LIMITS_H
|
||||
#define _VGMSTREAM_LIMITS_H
|
||||
|
||||
enum {
|
||||
/* Keep path limit within a reasonable stack size.
|
||||
* Windows generally only allows 260 chars in path, but other OSs have higher limits and we handle
|
||||
* UTF-8 (that typically uses 2-bytes for common non-latin codepages), plus player may append protocols
|
||||
* to paths. Most people wouldn't use huge paths though. */
|
||||
PATH_LIMIT = 4096, /* (256 * 8) * 2 = ~max_path * (other_os+extra) * codepage_bytes */
|
||||
|
||||
STREAM_NAME_SIZE = 256, /* usually small but may need to concat multiple names */
|
||||
VGMSTREAM_MAX_CHANNELS = 64, /* +40ch multilayers */
|
||||
VGMSTREAM_MIN_SAMPLE_RATE = 300, /* 300 is Wwise min */
|
||||
VGMSTREAM_MAX_SAMPLE_RATE = 192000, /* found in some FSB5 */
|
||||
VGMSTREAM_MAX_SUBSONGS = 65535, /* +20000 isn't that uncommon */
|
||||
VGMSTREAM_MAX_NUM_SAMPLES = 1000000000, /* no ~5h vgm hopefully */
|
||||
};
|
||||
|
||||
#endif
|
@ -19,18 +19,7 @@
|
||||
|
||||
|
||||
/* reasonable limits */
|
||||
enum {
|
||||
/* Windows generally only allows 260 chars in path, but other OSs have higher limits, and we handle
|
||||
* UTF-8 (that typically uses 2-bytes for common non-latin codepages) plus player may append protocols
|
||||
* to paths, so it should be a bit higher. Most people wouldn't use huge paths though. */
|
||||
PATH_LIMIT = 4096, /* (256 * 8) * 2 = ~max_path * (other_os+extra) * codepage_bytes */
|
||||
STREAM_NAME_SIZE = 255,
|
||||
VGMSTREAM_MAX_CHANNELS = 64,
|
||||
VGMSTREAM_MIN_SAMPLE_RATE = 300, /* 300 is Wwise min */
|
||||
VGMSTREAM_MAX_SAMPLE_RATE = 192000, /* found in some FSB5 */
|
||||
VGMSTREAM_MAX_SUBSONGS = 65535, /* +20000 isn't that uncommon */
|
||||
VGMSTREAM_MAX_NUM_SAMPLES = 1000000000, /* no ~5h vgm hopefully */
|
||||
};
|
||||
#include "util/vgmstream_limits.h"
|
||||
|
||||
#include "streamfile.h"
|
||||
#include "vgmstream_types.h"
|
||||
|
Loading…
Reference in New Issue
Block a user