Merge pull request #1555 from bnnm/fsb-etc

- Fix .fsb with XMA + basic headers [Forza 3 (X360)]
- Add FSB key
- cleanup
This commit is contained in:
bnnm 2024-07-07 21:47:38 +02:00 committed by GitHub
commit 34d8e84411
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 1115 additions and 579 deletions

View File

@ -280,6 +280,9 @@ vgmstream_cli: version
vgmstream123: version vgmstream123: version
$(MAKE) -C cli vgmstream123 $(MAKE) -C cli vgmstream123
api_example: version
$(MAKE) -C cli api_example
winamp: version winamp: version
$(MAKE) -C winamp in_vgmstream $(MAKE) -C winamp in_vgmstream

View File

@ -12,19 +12,17 @@ CFLAGS += $(DEF_CFLAGS) -DVAR_ARRAYS $(EXTRA_CFLAGS)
LDFLAGS += -L../src -lvgmstream -lm $(EXTRA_LDFLAGS) LDFLAGS += -L../src -lvgmstream -lm $(EXTRA_LDFLAGS)
TARGET_EXT_LIBS = TARGET_EXT_LIBS =
ifeq ($(TARGET_OS),Windows_NT) OUTPUT_CLI = vgmstream-cli
OUTPUT_CLI = vgmstream-cli.exe OUTPUT_123 = vgmstream123
OUTPUT_123 = vgmstream123.exe OUTPUT_API = api_example
ifeq ($(TARGET_OS),Windows_NT)
CFLAGS += -DWIN32 -I../ext_includes -I../ext_libs/Getopt CFLAGS += -DWIN32 -I../ext_includes -I../ext_libs/Getopt
LDFLAGS += -L../ext_libs/$(DLL_DIR) LDFLAGS += -L../ext_libs/$(DLL_DIR)
LIBAO_INC = -I$(LIBAO_IPATH) LIBAO_INC = -I$(LIBAO_IPATH)
LIBAO_LIB = -L$(LIBAO_LPATH) -lao LIBAO_LIB = -L$(LIBAO_LPATH) -lao
else else
OUTPUT_CLI = vgmstream-cli
OUTPUT_123 = vgmstream123
#todo move to subfolders and remove #todo move to subfolders and remove
CFLAGS += -I../ext_includes CFLAGS += -I../ext_includes
@ -50,6 +48,10 @@ vgmstream123: libvgmstream.a $(TARGET_EXT_LIBS)
$(CC) $(CFLAGS) $(LIBAO_INC) vgmstream123.c $(LDFLAGS) $(LIBAO_LIB) -o $(OUTPUT_123) $(CC) $(CFLAGS) $(LIBAO_INC) vgmstream123.c $(LDFLAGS) $(LIBAO_LIB) -o $(OUTPUT_123)
$(STRIP) $(OUTPUT_123) $(STRIP) $(OUTPUT_123)
api_example: libvgmstream.a $(TARGET_EXT_LIBS)
$(CC) $(CFLAGS) api_example.c $(LDFLAGS) -o $(OUTPUT_API)
$(STRIP) api_example
libvgmstream.a: libvgmstream.a:
$(MAKE) -C ../src $@ $(MAKE) -C ../src $@
@ -57,6 +59,6 @@ $(TARGET_EXT_LIBS):
$(MAKE) -C ../ext_libs $@ $(MAKE) -C ../ext_libs $@
clean: clean:
$(RMF) $(OUTPUT_CLI) $(OUTPUT_123) $(RMF) $(OUTPUT_CLI) $(OUTPUT_123) $(OUTPUT_123)
.PHONY: clean vgmstream_cli libvgmstream.a $(TARGET_EXT_LIBS) .PHONY: clean vgmstream_cli libvgmstream.a $(TARGET_EXT_LIBS)

112
cli/api_example.c Normal file
View File

@ -0,0 +1,112 @@
#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

View File

@ -902,7 +902,7 @@ static int write_file(VGMSTREAM* vgmstream, cli_config* cfg) {
render_vgmstream(buf, to_get, vgmstream); render_vgmstream(buf, to_get, vgmstream);
swap_samples_le(buf, channels * to_get); /* write PC endian */ swap_samples_le(buf, channels * to_get); /* write PC endian */
fwrite(buf, sizeof(sample_t) * channels, to_get, outfile); fwrite(buf, sizeof(sample_t), to_get * channels, outfile);
/* should write infinitely until program kill */ /* should write infinitely until program kill */
} }
@ -930,7 +930,7 @@ static int write_file(VGMSTREAM* vgmstream, cli_config* cfg) {
if (!cfg->decode_only) { if (!cfg->decode_only) {
swap_samples_le(buf, channels * to_get); /* write PC endian */ swap_samples_le(buf, channels * to_get); /* write PC endian */
fwrite(buf, sizeof(sample_t) * channels, to_get, outfile); fwrite(buf, sizeof(sample_t), to_get * channels, outfile);
} }
} }

View File

@ -1,6 +1,8 @@
# vgmstream lib build help # vgmstream lib build help
This document explains how to build various external dependencies used in vgmstream. Like *vgmstream*, most external libs use C and need to be compiled as such. This document explains how to build various external dependencies used in vgmstream. Like *vgmstream*, most external libs use C and need to be compiled as such.
The main purpose this doc is to have a reference of what each lib is doing, and to rebuild Windows DLLs. Linux libs are handled automatically using CMake, though you can use these steps too.
See [BUILD](BUILD.md#external-libraries) for a description of each lib first. See [BUILD](BUILD.md#external-libraries) for a description of each lib first.
## Intro ## Intro
@ -60,7 +62,7 @@ On Linux `install` is used to actually *install* libs on system dirs (so `--pref
You can call multiple *targets* in a single line `make clean install-strip` is the same as `make clean` and `make install` (which in turn calls plain `make` / default). That's the theory, but at some libs don't properly handle this. You can call multiple *targets* in a single line `make clean install-strip` is the same as `make clean` and `make install` (which in turn calls plain `make` / default). That's the theory, but at some libs don't properly handle this.
### autotools config ### autotools config
*autotools* are **very** fragile and picky, beware when changing stuff.Check other flags by calling `sh ./configure --help`, but changing some of the steps will likely cause odd issues, *autotools are not consistent between libs*. *autotools* are **very** fragile and picky, beware when changing stuff. Check other flags by calling `sh ./configure --help`, but changing some of the steps will likely cause odd issues. *autotools are not consistent between libs*.
Common *configure*/Makefile params: Common *configure*/Makefile params:
- `--build=...`: current compilation environment (autodetected, but may fail in outdated libs) - `--build=...`: current compilation environment (autodetected, but may fail in outdated libs)
@ -90,7 +92,7 @@ However, *those flags aren't consistent between libs*, meaning in one using *con
### Xiph's releases and exports ### Xiph's releases and exports
Sometimes we use "official releases" sources rather than using Git's sources. Both should be the same, but releases have pre-generated *./configure*, while Git needs to call `autogen.sh` that calls `autoreconf` that generates a base `configure` script. Since getting `autoreconf` working on **Windows** without MSYS2 requires extra steps (not described), Xiph's releases are recommended. Sometimes we use "official releases" sources rather than using Git's sources. Both should be the same, but releases have pre-generated *./configure*, while Git needs to call `autogen.sh` that calls `autoreconf` that generates a base `configure` script. Since getting `autoreconf` working on **Windows** without MSYS2 requires extra steps (not described), Xiph's releases are recommended.
When building a DLL/lib compiler sets *exported symbols* (functions). Xiph's *autoconf* may generate DLLs correctly, butdon't detect Mingw/Win config properly and export all symbols by default. This is fixed manually, but there may be better ways to handle it (to be researched). When building a DLL/lib compiler sets *exported symbols* (functions). Xiph's *autoconf* may generate DLLs correctly, but don't detect Mingw/Win config properly and export all symbols by default. This is fixed manually, but there may be better ways to handle it (to be researched).
### Shared libs details ### Shared libs details
Roughly, a `.dll` is a Windows "shared library"; Linux equivalent would be a `.so` file. First, `.c` files are compiled into objects (`.o` in GCC, `.obj` in MSCV), then can be made into a `.dll`. Later, when a program needs that DLL (or rather, it's functions), a compiler can use it as long as some conditions are met. Roughly, a `.dll` is a Windows "shared library"; Linux equivalent would be a `.so` file. First, `.c` files are compiled into objects (`.o` in GCC, `.obj` in MSCV), then can be made into a `.dll`. Later, when a program needs that DLL (or rather, it's functions), a compiler can use it as long as some conditions are met.
@ -98,7 +100,7 @@ Roughly, a `.dll` is a Windows "shared library"; Linux equivalent would be a `.s
DLL must *export symbols* (functions), which on a Windows's DLL is done with: DLL must *export symbols* (functions), which on a Windows's DLL is done with:
- adding `__declspec(dllexport)` to a function (usually done with `#define EXPORT ...` and similar ways) - adding `__declspec(dllexport)` to a function (usually done with `#define EXPORT ...` and similar ways)
- using a `.def` module definition file - using a `.def` module definition file
- if neither of the above is used, GCC exports every function by default (not great= - if neither of the above is used, GCC exports every function by default (not great)
Then, to *link* (refer to) a DLL compiler usually needs helper files (`.dll.a` in GCC, `.lib` in MSVC). DLL's are copied to vgmstream's source, while helper files are created on compile time from `.dll`+`.def` (see *ext_libs/Makefile* for GCC and `ext_libs.vcxproj` for MSVC). Then, to *link* (refer to) a DLL compiler usually needs helper files (`.dll.a` in GCC, `.lib` in MSVC). DLL's are copied to vgmstream's source, while helper files are created on compile time from `.dll`+`.def` (see *ext_libs/Makefile* for GCC and `ext_libs.vcxproj` for MSVC).

136
src/api.h
View File

@ -1,26 +1,29 @@
#ifndef _API_H_ #ifndef _API_H_
#define _API_H_ #define _API_H_
#include "base/plugins.h" //TODO: to be removed
#include "base/plugins.h"
//possible future public/opaque API
#if 0 #if 0
/* vgmstream's public API
* basic usage (also see api_example.c):
* - libvgmstream_get_version() // if needed
* - libvgmstream_init(...) // base context
* - libvgmstream_setup(...) // standard config
* - libvgmstream_open(...) // check detected format
* - libvgmstream_play(...) // main decode
* - output samples + repeat libvgmstream_play until stream is done
* - libvgmstream_free(...) // cleanup
*/
#include <stdint.h> #include <stdint.h>
#include "api_streamfile.h" #include <stdbool.h>
/* Current API version (major=breaking API/ABI changes, minor=compatible ABI changes). /* standard C param call and name mangling (to avoid __stdcall / .defs) */
* Internal bug fixes or added formats don't change these (see commit revision).
* Regular vgmstream features or formats are stable and are rarely removed, while this API may change from time to time */
#define LIBVGMSTREAM_API_VERSION_MAJOR 0
#define LIBVGMSTREAM_API_VERSION_MINOR 0
/* define standard C param call and name mangling (to avoid __stdcall / .defs) */
//#define LIBVGMSTREAM_CALL __cdecl //needed? //#define LIBVGMSTREAM_CALL __cdecl //needed?
//LIBVGMSTREAM_API (type) LIBVGMSTREAM_CALL libvgmstream_function(...);
/* define external function types (during compilation) */
//LIBVGMSTREAM_API void LIBVGMSTREAM_CALL vgmstream_function(void); /* define external function behavior (during compilation) */
#if defined(LIBVGMSTREAM_EXPORT) #if defined(LIBVGMSTREAM_EXPORT)
#define LIBVGMSTREAM_API __declspec(dllexport) /* when exporting/creating vgmstream DLL */ #define LIBVGMSTREAM_API __declspec(dllexport) /* when exporting/creating vgmstream DLL */
#elif defined(LIBVGMSTREAM_IMPORT) #elif defined(LIBVGMSTREAM_IMPORT)
@ -29,104 +32,17 @@
#define LIBVGMSTREAM_API /* nothing, internal/default */ #define LIBVGMSTREAM_API /* nothing, internal/default */
#endif #endif
/* opaque vgmstream context/handle */
typedef struct libvgmstream_t libvgmstream_t;
/* init base vgmstream context */
libvgmstream_t* libvgmstream_init(void);
typedef struct {
int downmix_max_channels; // max number of channels
//int upmix_min_channels; // adds channels until min
} libvgmstream_config_t;
/* pass default config, that will be applied to song on open (some formats like TXTP may override
* these settings).
* May only be called without song loaded (before _open or after _close), otherwise ignored. */
void libvgmstream_setup(libvgmstream_t* vctx, libvgmstream_config_t* vcfg);
//void libvgmstream_buffer(libvgmstream_t* vctx, int samples, int max_samples);
/* Opens a new STREAMFILE to play. Returns < 0 on error when the file isn't recogniced.
* If file has subsongs, first open usually loads first subsong. get_info then can be used to check
* whether file has more subsongs (total_subsongs > 1), and call others.
* */
int libvgmstream_open(libvgmstream_t* vctx, STREAMFILE* sf);
int libvgmstream_open_subsong(libvgmstream_t* vctx, STREAMFILE* sf, int subsong);
typedef struct {
const int channels;
const int sample_rate;
const int sample_count; /* file's samples (not final duration) */
const int loop_start_sample;
const int loop_end_sample;
const int loop_flag;
const int current_subsong; /* 0=not set, N=loaded subsong N */
const int total_subsongs; /* 0=format has no subsongs, N=has N subsongs */
const int file_bitrate; /* file's average bitrate */
//const int codec_bitrate; /* codec's average bitrate */
/* descriptions */
//const char* codec;
//const char* layout;
//const char* metadata;
//int type; /* 0=pcm16, 1=float32, always interleaved: [0]=ch0, [1]=ch1 ... */
} libvgmstream_into_t;
/* Get info from current song. */
void libvgmstream_t_get_info(libvgmstream_t* vctx, libvgmstream_into_t* vinfo);
libvgmstream_sbuf_t* libgstream_get_sbuf(libvgmstream_t* vctx);
/* Converts samples. returns number of rendered samples, or <=0 if no more
* samples left (will fill buffer with silence) */
int libvgmstream_play(libvgmstream_t* vctx);
/* Gets final time based on config and current song. If config is set to "play forever"
* this still returns final time based on config as a reference. Returns > 0 on success. */
int32_t libvgmstream_get_total_time(libvgmstream_t* vctx);
double libvgmstream_get_total_samples(libvgmstream_t* vctx);
/* Gets current position within song. When "play forever" is set, it'll clamp results to total_time. */
int32_t libvgmstream_get_current_time(libvgmstream_t* vctx);
double libvgmstream_get_current_samples(libvgmstream_t* vctx);
/* Seeks to position */
libvgmstream_t* libvgmstream_seek_absolute_sample(libvgmstream_t* vctx, int32_t sample);
libvgmstream_t* libvgmstream_seek_absolute_time(libvgmstream_t* vctx, double time);
libvgmstream_t* libvgmstream_seek_current_sample(libvgmstream_t* vctx, int32_t sample);
libvgmstream_t* libvgmstream_seek_current_time(libvgmstream_t* vctx, double time);
/* Closes current song. */
void libvgmstream_close(libvgmstream_t* vctx);
/* Frees vgmstream context. */
void libvgmstream_free(libvgmstream_t* vctx);
#if 0
void vgmstream_get_buffer(...);
void vgmstream_format_check(...);
void vgmstream_set_format_whilelist(...);
void vgmstream_set_format_blacklist(...);
const char* vgmstream_describe(...);
const char* vgmstream_get_title(...);
VGMSTREAM_TAGS* vgmstream_get_tagfile(...);
#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. */
#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"
#include "api_streamfile.h"
#include "api_tags.h"
#endif #endif
#endif #endif

156
src/api_main.c Normal file
View File

@ -0,0 +1,156 @@
#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 Normal file
View File

@ -0,0 +1,235 @@
#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

51
src/api_streamfile.h Normal file
View File

@ -0,0 +1,51 @@
#if 0
#ifndef _API_STREAMFILE_H_
#define _API_STREAMFILE_H_
/* 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
*
* 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.
*/
#include "api.h"
// TODO: pass opaque instead?
typedef struct libvgmstream_streamfile_t {
/* user data */
void* opaque;
/* 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);
/* get max offset */
size_t (*get_size)(struct libvgmstream_streamfile_t* sf); //TODO return int64_t?
/* copy current filename to name buf */
void (*get_name)(struct libvgmstream_streamfile_t* sf, char* name, size_t name_size);
/* 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);
/* free current STREAMFILE */
void (*close)(struct libvgmstream_streamfile_t* sf);
} libvgmstream_streamfile_t;
/* helper */
static inline void libvgmstream_streamfile_close(libvgmstream_streamfile_t* sf) {
if (!sf)
return;
sf->close(sf);
}
//LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_get_file(const char* filename);
#endif
#endif

40
src/api_tags.h Normal file
View File

@ -0,0 +1,40 @@
#if 0
#ifndef _API_TAGS_H_
#define _API_TAGS_H_
#include "api.h"
#include "api_streamfile.h"
/* vgmstream's !tags.m3u API.
* Doesn't need a main libvgmstream lib as tags aren't tied to loaded songs.
*/
/* 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.
* - 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);
/* Extracts next valid tag in tagfile to key/val.
* - returns 0 if no more tags are found (meant to be called repeatedly until 0)
* - 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);
/* Closes tags. */
LIBVGMSTREAM_API void libvgmstream_tags_free(libvgmstream_tags_t* tags);
#endif
#endif

View File

@ -430,10 +430,26 @@ fail:
return NULL; return NULL;
} }
/* FFmpeg internals (roughly) for reference:
*
* AVFormatContext // base info extracted from input file
* AVStream // substreams
* AVCodecParameters // codec id, channels, format, ...
*
* AVCodecContext // sample rate and general info
*
* - open avformat to get all possible format info (needs file or custom IO)
* - open avcodec based on target stream + codec info from avformat
* - decode chunks of data (feed style)
* - read next frame into packet via avformat
* - decode packet via avcodec
* - handle samples
*/
static int init_ffmpeg_config(ffmpeg_codec_data* data, int target_subsong, int reset) { static int init_ffmpeg_config(ffmpeg_codec_data* data, int target_subsong, int reset) {
int errcode = 0; int errcode = 0;
/* basic IO/format setup */ /* custom IO/format setup */
data->buffer = av_malloc(FFMPEG_DEFAULT_IO_BUFFER_SIZE); data->buffer = av_malloc(FFMPEG_DEFAULT_IO_BUFFER_SIZE);
if (!data->buffer) goto fail; if (!data->buffer) goto fail;
@ -448,13 +464,14 @@ static int init_ffmpeg_config(ffmpeg_codec_data* data, int target_subsong, int r
//data->inputFormatCtx = av_find_input_format("h264"); /* set directly? */ //data->inputFormatCtx = av_find_input_format("h264"); /* set directly? */
/* on reset could use AVFormatContext.iformat to reload old format too */ /* on reset could use AVFormatContext.iformat to reload old format too */
/* format detection */
errcode = avformat_open_input(&data->formatCtx, NULL /*""*/, NULL, NULL); errcode = avformat_open_input(&data->formatCtx, NULL /*""*/, NULL, NULL);
if (errcode < 0) goto fail; if (errcode < 0) goto fail;
errcode = avformat_find_stream_info(data->formatCtx, NULL); errcode = avformat_find_stream_info(data->formatCtx, NULL);
if (errcode < 0) goto fail; if (errcode < 0) goto fail;
/* find valid audio stream and set other streams to discard */ /* find valid audio stream and set other streams to be discarded */
{ {
int i, stream_index, stream_count; int i, stream_index, stream_count;
@ -485,24 +502,22 @@ static int init_ffmpeg_config(ffmpeg_codec_data* data, int target_subsong, int r
data->stream_count = stream_count; data->stream_count = stream_count;
} }
/* setup codec with stream info */ /* setup codec from stream info */
data->codecCtx = avcodec_alloc_context3(NULL); data->codecCtx = avcodec_alloc_context3(NULL);
if (!data->codecCtx) goto fail; if (!data->codecCtx) goto fail;
errcode = avcodec_parameters_to_context(data->codecCtx, data->formatCtx->streams[data->stream_index]->codecpar); errcode = avcodec_parameters_to_context(data->codecCtx, data->formatCtx->streams[data->stream_index]->codecpar);
if (errcode < 0) goto fail; if (errcode < 0) goto fail;
/* deprecated and seemingly not needed */ //av_codec_set_pkt_timebase(data->codecCtx, stream->time_base); /* deprecated and seemingly not needed */
//av_codec_set_pkt_timebase(data->codecCtx, stream->time_base);
/* not useddeprecated and seemingly not needed */
data->codec = avcodec_find_decoder(data->codecCtx->codec_id); data->codec = avcodec_find_decoder(data->codecCtx->codec_id);
if (!data->codec) goto fail; if (!data->codec) goto fail;
errcode = avcodec_open2(data->codecCtx, data->codec, NULL); errcode = avcodec_open2(data->codecCtx, data->codec, NULL);
if (errcode < 0) goto fail; if (errcode < 0) goto fail;
/* prepare codec and frame/packet buffers */ /* prepare frame/packet buffers */
data->packet = av_malloc(sizeof(AVPacket)); /* av_packet_alloc? */ data->packet = av_malloc(sizeof(AVPacket)); /* av_packet_alloc? */
if (!data->packet) goto fail; if (!data->packet) goto fail;
av_new_packet(data->packet, 0); av_new_packet(data->packet, 0);

View File

@ -641,7 +641,6 @@ static const char* extension_list[] = {
"wbk", "wbk",
"wd", "wd",
"wem", "wem",
"wii",
"wiive", //txth/semi [Rubik World (Wii)] "wiive", //txth/semi [Rubik World (Wii)]
"wic", //txth/reserved [Road Rash (SAT)-videos] "wic", //txth/reserved [Road Rash (SAT)-videos]
"wip", //txth/reserved [Colin McRae DiRT (PC)] "wip", //txth/reserved [Colin McRae DiRT (PC)]

View File

@ -4,86 +4,23 @@
#include "fsb_interleave_streamfile.h" #include "fsb_interleave_streamfile.h"
/* ************************************************************************************************ typedef enum { MPEG, XBOX_IMA, FSB_IMA, PSX, XMA1, XMA2, DSP, CELT, PCM8, PCM8U, PCM16LE, PCM16BE } fsb_codec_t;
* FSB defines, copied from the public spec (https://www.fmod.org/questions/question/forum-4928/)
* for reference. The format is mostly compatible for FSB1/2/3/4, but not FSB5.
* ************************************************************************************************ */
/* These flags are used for FMOD_FSB_HEADER::mode */
#define FMOD_FSB_SOURCE_FORMAT 0x00000001 /* all samples stored in their original compressed format */
#define FMOD_FSB_SOURCE_BASICHEADERS 0x00000002 /* samples should use the basic header structure */
#define FMOD_FSB_SOURCE_ENCRYPTED 0x00000004 /* all sample data is encrypted */
#define FMOD_FSB_SOURCE_BIGENDIANPCM 0x00000008 /* pcm samples have been written out in big-endian format */
#define FMOD_FSB_SOURCE_NOTINTERLEAVED 0x00000010 /* Sample data is not interleaved. */
#define FMOD_FSB_SOURCE_MPEG_PADDED 0x00000020 /* Mpeg frames are now rounded up to the nearest 2 bytes for normal sounds, or 16 bytes for multichannel. */
#define FMOD_FSB_SOURCE_MPEG_PADDED4 0x00000040 /* Mpeg frames are now rounded up to the nearest 4 bytes for normal sounds, or 16 bytes for multichannel. */
/* These flags are used for FMOD_FSB_HEADER::version */
#define FMOD_FSB_VERSION_3_0 0x00030000 /* FSB version 3.0 */
#define FMOD_FSB_VERSION_3_1 0x00030001 /* FSB version 3.1 */
#define FMOD_FSB_VERSION_4_0 0x00040000 /* FSB version 4.0 */
/* FMOD 3 defines. These flags are used for FMOD_FSB_SAMPLE_HEADER::mode */
#define FSOUND_LOOP_OFF 0x00000001 /* For non looping samples. */
#define FSOUND_LOOP_NORMAL 0x00000002 /* For forward looping samples. */
#define FSOUND_LOOP_BIDI 0x00000004 /* For bidirectional looping samples. (no effect if in hardware). */
#define FSOUND_8BITS 0x00000008 /* For 8 bit samples. */
#define FSOUND_16BITS 0x00000010 /* For 16 bit samples. */
#define FSOUND_MONO 0x00000020 /* For mono samples. */
#define FSOUND_STEREO 0x00000040 /* For stereo samples. */
#define FSOUND_UNSIGNED 0x00000080 /* For user created source data containing unsigned samples. */
#define FSOUND_SIGNED 0x00000100 /* For user created source data containing signed data. */
#define FSOUND_MPEG 0x00000200 /* For MPEG layer 2/3 data. */
#define FSOUND_CHANNELMODE_ALLMONO 0x00000400 /* Sample is a collection of mono channels. */
#define FSOUND_CHANNELMODE_ALLSTEREO 0x00000800 /* Sample is a collection of stereo channel pairs */
#define FSOUND_HW3D 0x00001000 /* Attempts to make samples use 3d hardware acceleration. (if the card supports it) */
#define FSOUND_2D 0x00002000 /* Tells software (not hardware) based sample not to be included in 3d processing. */
#define FSOUND_SYNCPOINTS_NONAMES 0x00004000 /* Specifies that syncpoints are present with no names */
#define FSOUND_DUPLICATE 0x00008000 /* This subsound is a duplicate of the previous one i.e. it uses the same sample data but w/different mode bits */
#define FSOUND_CHANNELMODE_PROTOOLS 0x00010000 /* Sample is 6ch and uses L C R LS RS LFE standard. */
#define FSOUND_MPEGACCURATE 0x00020000 /* For FSOUND_Stream_Open - for accurate FSOUND_Stream_GetLengthMs/FSOUND_Stream_SetTime. WARNING, see FSOUND_Stream_Open for inital opening time performance issues. */
#define FSOUND_HW2D 0x00080000 /* 2D hardware sounds. allows hardware specific effects */
#define FSOUND_3D 0x00100000 /* 3D software sounds */
#define FSOUND_32BITS 0x00200000 /* For 32 bit (float) samples. */
#define FSOUND_IMAADPCM 0x00400000 /* Contents are stored compressed as IMA ADPCM */
#define FSOUND_VAG 0x00800000 /* For PS2 only - Contents are compressed as Sony VAG format */
#define FSOUND_XMA 0x01000000 /* For Xbox360 only - Contents are compressed as XMA format */
#define FSOUND_GCADPCM 0x02000000 /* For Gamecube only - Contents are compressed as Gamecube DSP-ADPCM format */
#define FSOUND_MULTICHANNEL 0x04000000 /* For PS2 and Gamecube only - Contents are interleaved into a multi-channel (more than stereo) format */
#define FSOUND_OGG 0x08000000 /* For vorbis encoded ogg data */
#define FSOUND_CELT 0x08000000 /* For vorbis encoded ogg data */
#define FSOUND_MPEG_LAYER3 0x10000000 /* Data is in MP3 format. */
#define FSOUND_MPEG_LAYER2 0x00040000 /* Data is in MP2 format. */
#define FSOUND_LOADMEMORYIOP 0x20000000 /* For PS2 only - &quot;name&quot; will be interpreted as a pointer to data for streaming and samples. The address provided will be an IOP address */
#define FSOUND_IMAADPCMSTEREO 0x20000000 /* Signify IMA ADPCM is actually stereo not two interleaved mono */
#define FSOUND_IGNORETAGS 0x40000000 /* Skips id3v2 etc tag checks when opening a stream, to reduce seek/read overhead when opening files (helps with CD performance) */
#define FSOUND_SYNCPOINTS 0x80000000 /* Specifies that syncpoints are present */
/* These flags are used for FMOD_FSB_SAMPLE_HEADER::mode */
#define FSOUND_CHANNELMODE_MASK (FSOUND_CHANNELMODE_ALLMONO | FSOUND_CHANNELMODE_ALLSTEREO | FSOUND_CHANNELMODE_PROTOOLS)
#define FSOUND_CHANNELMODE_DEFAULT 0x00000000 /* Determine channel assignment automatically from channel count. */
#define FSOUND_CHANNELMODE_RESERVED 0x00000C00
#define FSOUND_NORMAL (FSOUND_16BITS | FSOUND_SIGNED | FSOUND_MONO)
#define FSB_SAMPLE_DATA_ALIGN 32
/* simplified struct based on the original definitions */
typedef enum { MPEG, IMA, PSX, XMA, DSP, CELT, PCM8, PCM16 } fsb_codec_t;
typedef struct { typedef struct {
/* main header */ /* main header */
uint32_t id; uint32_t id;
int32_t total_subsongs; int32_t total_subsongs;
uint32_t sample_headers_size; /* all of them including extended information */ uint32_t sample_headers_size;
uint32_t sample_data_size; uint32_t sample_data_size;
uint32_t version; /* extended fsb version (in FSB 3/3.1/4) */ uint32_t version; /* extended fsb version (in FSB 3/3.1/4) */
uint32_t flags; /* flags common to all streams (in FSB 3/3.1/4)*/ uint32_t flags; /* flags common to all streams (in FSB 3/3.1/4) */
/* sample header */ /* sample header */
uint32_t num_samples;
uint32_t stream_size; uint32_t stream_size;
uint32_t loop_start; int32_t num_samples;
uint32_t loop_end; int32_t loop_start;
uint32_t mode; int32_t loop_end;
int32_t sample_rate; int32_t sample_rate;
uint16_t channels; uint16_t channels;
uint32_t mode; /* flags for current stream */
/* extra */ /* extra */
uint32_t base_header_size; uint32_t base_header_size;
uint32_t sample_header_min; uint32_t sample_header_min;
@ -94,245 +31,44 @@ typedef struct {
off_t name_offset; off_t name_offset;
size_t name_size; size_t name_size;
int loop_flag; int mpeg_padding;
bool non_interleaved;
bool loop_flag;
off_t stream_offset; off_t stream_offset;
fsb_codec_t codec; fsb_codec_t codec;
} fsb_header; } fsb_header_t;
/* ********************************************************************************** */ static bool parse_fsb(fsb_header_t* fsb, STREAMFILE* sf);
static layered_layout_data* build_layered_fsb_celt(STREAMFILE* sf, fsb_header_t* fsb, bool is_new_lib);
static layered_layout_data* build_layered_fsb_celt(STREAMFILE* sf, fsb_header* fsb, int is_new_lib);
/* FSB1~4 - from games using FMOD audio middleware */ /* FSB1~4 - from games using FMOD audio middleware */
VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) { VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
int target_subsong = sf->stream_index; fsb_header_t fsb = {0};
fsb_header fsb = {0};
/* checks */ /* checks */
if ((read_u32be(0x00,sf) & 0xFFFFFF00) != get_id32be("FSB\0")) uint32_t id = read_u32be(0x00,sf);
goto fail; if (id < get_id32be("FSB1") || id > get_id32be("FSB4"))
return NULL;
/* .fsb: standard /* .fsb: standard
* .bnk: Hard Corps Uprising (PS3) * .bnk: Hard Corps Uprising (PS3)
* .sfx: Geon Cube (Wii) * .sfx: Geon Cube (Wii)
* .ps3: Neversoft games (PS3) * .ps3: Neversoft games (PS3)
* .xen: Neversoft games (X360/PC) */ * .xen: Neversoft games (X360/PC) */
if ( !check_extensions(sf, "fsb,bnk,sfx,ps3,xen") ) if (!check_extensions(sf, "fsb,bnk,sfx,ps3,xen"))
goto fail; return NULL;
fsb.id = read_u32be(0x00,sf); if (!parse_fsb(&fsb, sf))
if (fsb.id == get_id32be("FSB1")) { return NULL;
fsb.meta_type = meta_FSB1;
fsb.base_header_size = 0x10;
fsb.sample_header_min = 0x40;
/* main header */
fsb.total_subsongs = read_32bitLE(0x04,sf);
fsb.sample_data_size = read_32bitLE(0x08,sf);
fsb.sample_headers_size = 0x40;
fsb.version = 0;
fsb.flags = 0;
if (fsb.total_subsongs > 1)
goto fail;
/* sample header (first stream only, not sure if there are multi-FSB1) */
{
off_t header_offset = fsb.base_header_size;
fsb.name_offset = header_offset;
fsb.name_size = 0x20;
fsb.num_samples = read_32bitLE(header_offset+0x20,sf);
fsb.stream_size = read_32bitLE(header_offset+0x24,sf);
fsb.sample_rate = read_32bitLE(header_offset+0x28,sf);
/* 0x2c:? 0x2e:? 0x30:? 0x32:? */
fsb.mode = read_32bitLE(header_offset+0x34,sf);
fsb.loop_start = read_32bitLE(header_offset+0x38,sf);
fsb.loop_end = read_32bitLE(header_offset+0x3c,sf);
VGM_ASSERT(fsb.loop_end > fsb.num_samples, "FSB: loop end over samples (%i vs %i)\n", fsb.loop_end, fsb.num_samples);
fsb.channels = (fsb.mode & FSOUND_STEREO) ? 2 : 1;
if (fsb.loop_end > fsb.num_samples) /* this seems common... */
fsb.num_samples = fsb.loop_end;
/* DSP coefs, seek tables, etc */
fsb.extradata_offset = header_offset+fsb.sample_header_min;
fsb.stream_offset = fsb.base_header_size + fsb.sample_headers_size;
}
}
else {
if (fsb.id == get_id32be("FSB2")) {
fsb.meta_type = meta_FSB2;
fsb.base_header_size = 0x10;
fsb.sample_header_min = 0x40; /* guessed */
} else if (fsb.id == get_id32be("FSB3")) {
fsb.meta_type = meta_FSB3;
fsb.base_header_size = 0x18;
fsb.sample_header_min = 0x40;
} else if (fsb.id == get_id32be("FSB4")) {
fsb.meta_type = meta_FSB4;
fsb.base_header_size = 0x30;
fsb.sample_header_min = 0x50;
} else {
goto fail;
}
/* main header */
fsb.total_subsongs = read_32bitLE(0x04,sf);
fsb.sample_headers_size = read_32bitLE(0x08,sf);
fsb.sample_data_size = read_32bitLE(0x0c,sf);
if (fsb.base_header_size > 0x10) {
fsb.version = read_32bitLE(0x10,sf);
fsb.flags = read_32bitLE(0x14,sf);
/* FSB4: 0x18(8):hash, 0x20(10):guid */
} else {
fsb.version = 0;
fsb.flags = 0;
}
if (fsb.version == FMOD_FSB_VERSION_3_1) {
fsb.sample_header_min = 0x50;
} else if (fsb.version != 0 /* FSB2 */
&& fsb.version != FMOD_FSB_VERSION_3_0
&& fsb.version != FMOD_FSB_VERSION_4_0) {
goto fail;
}
if (fsb.sample_headers_size < fsb.sample_header_min) goto fail;
if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > fsb.total_subsongs || fsb.total_subsongs < 1) goto fail;
/* sample header (N-stream) */
{
int i;
off_t header_offset = fsb.base_header_size;
off_t data_offset = fsb.base_header_size + fsb.sample_headers_size;
/* find target_stream header (variable sized) */
for (i = 0; i < fsb.total_subsongs; i++) {
size_t stream_header_size;
if ((fsb.flags & FMOD_FSB_SOURCE_BASICHEADERS) && i > 0) {
/* miniheader, all subsongs reuse first header [rare, ex. Biker Mice from Mars (PS2)] */
stream_header_size = 0x08;
fsb.num_samples = read_32bitLE(header_offset+0x00,sf);
fsb.stream_size = read_32bitLE(header_offset+0x04,sf);
fsb.loop_start = 0;
fsb.loop_end = 0;
}
else {
/* subsong header for normal files */
stream_header_size = read_u16le(header_offset+0x00,sf);
fsb.name_offset = header_offset+0x02;
fsb.name_size = 0x20-0x02;
fsb.num_samples = read_32bitLE(header_offset+0x20,sf);
fsb.stream_size = read_32bitLE(header_offset+0x24,sf);
fsb.loop_start = read_32bitLE(header_offset+0x28,sf);
fsb.loop_end = read_32bitLE(header_offset+0x2c,sf);
fsb.mode = read_32bitLE(header_offset+0x30,sf);
fsb.sample_rate = read_32bitLE(header_offset+0x34,sf);
/* 0x38: defvol, 0x3a: defpan, 0x3c: defpri */
fsb.channels = read_u16le(header_offset+0x3e,sf);
/* FSB3.1/4:
* 0x40: mindistance, 0x44: maxdistance, 0x48: varfreq/size_32bits
* 0x4c: varvol, 0x4e: fsb.varpan */
/* DSP coefs, seek tables, etc */
if (stream_header_size > fsb.sample_header_min) {
fsb.extradata_offset = header_offset+fsb.sample_header_min;
if (fsb.first_extradata_offset == 0)
fsb.first_extradata_offset = fsb.extradata_offset;
}
}
if (i+1 == target_subsong) /* final data_offset found */
break;
header_offset += stream_header_size;
data_offset += fsb.stream_size; /* there is no offset so manually count */
/* some subsongs offsets need padding (most FSOUND_IMAADPCM, few MPEG too [Hard Reset (PC) subsong 5])
* other codecs may set PADDED4 (ex. XMA) but don't seem to need it and work fine */
if (fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED4) {
if (data_offset % 0x20)
data_offset += 0x20 - (data_offset % 0x20);
}
}
if (i > fsb.total_subsongs)
goto fail; /* not found */
fsb.stream_offset = data_offset;
}
}
/* convert to clean some code */
if (fsb.mode & FSOUND_MPEG) fsb.codec = MPEG;
else if (fsb.mode & FSOUND_IMAADPCM) fsb.codec = IMA;
else if (fsb.mode & FSOUND_VAG) fsb.codec = PSX;
else if (fsb.mode & FSOUND_XMA) fsb.codec = XMA;
else if (fsb.mode & FSOUND_GCADPCM) fsb.codec = DSP;
else if (fsb.mode & FSOUND_CELT) fsb.codec = CELT;
else if (fsb.mode & FSOUND_8BITS) fsb.codec = PCM8;
else fsb.codec = PCM16;
/* correct compared to FMOD's tools */
if (fsb.loop_end)
fsb.loop_end += 1;
/* ping-pong looping = no looping? (forward > reverse > forward) [ex. Biker Mice from Mars (PS2)] */
VGM_ASSERT(fsb.mode & FSOUND_LOOP_BIDI, "FSB BIDI looping found\n");
VGM_ASSERT(fsb.mode & FSOUND_LOOP_OFF, "FSB LOOP OFF found\n"); /* sometimes used */
VGM_ASSERT(fsb.mode & FSOUND_LOOP_NORMAL, "FSB LOOP NORMAL found\n"); /* very rarely set */
/* XOR encryption for some FSB4, though the flag is only seen after decrypting */
//;VGM_ASSERT(fsb.flags & FMOD_FSB_SOURCE_ENCRYPTED, "FSB ENCRYPTED found\n");
/* sometimes there is garbage at the end or missing bytes due to improper ripping */
vgm_asserti(fsb.base_header_size + fsb.sample_headers_size + fsb.sample_data_size != get_streamfile_size(sf),
"FSB wrong head/data_size found (expected 0x%x vs 0x%x)\n",
fsb.base_header_size + fsb.sample_headers_size + fsb.sample_data_size, (uint32_t)get_streamfile_size(sf));
/* autodetect unwanted loops */
{
/* FMOD tool's default behaviour is creating files with full loops and no flags unless disabled
* manually (can be overriden during program too), for all FSB versions. This makes jingles/sfx/voices
* loop when they shouldn't, but most music does full loops seamlessly, so we only want to disable
* if it looks jingly enough. Incidentally, their tools can only make files with full loops. */
int enable_loop, full_loop, is_small;
/* seems to mean forced loop */
enable_loop = (fsb.mode & FSOUND_LOOP_NORMAL);
/* for MPEG and CELT sometimes full loops are given with around/exact 1 frame less than num_samples,
* probably to account for encoder/decoder delay (ex. The Witcher 2, Hard Reset, Timeshift) */
if (fsb.codec == CELT)
full_loop = fsb.loop_start - 512 <= 0 && fsb.loop_end >= fsb.num_samples - 512; /* aproximate */
else if (fsb.codec == MPEG)
full_loop = fsb.loop_start - 1152 <= 0 && fsb.loop_end >= fsb.num_samples - 1152; /* WWF Legends of Wrestlemania uses 2 frames? */
else
full_loop = fsb.loop_start == 0 && fsb.loop_end == fsb.num_samples;
/* in seconds (lame but no better way) */
is_small = fsb.num_samples < 20 * fsb.sample_rate;
//;VGM_LOG("FSB: loop start=%i, loop end=%i, samples=%i, mode=%x\n", fsb.loop_start, fsb.loop_end, fsb.num_samples, fsb.mode);
//;VGM_LOG("FSB: enable=%i, full=%i, small=%i\n",enable_loop,full_loop,is_small );
fsb.loop_flag = !(fsb.mode & FSOUND_LOOP_OFF); /* disabled manually */
if (fsb.loop_flag && !enable_loop && full_loop && is_small) {
VGM_LOG("FSB: disabled unwanted loop\n");
fsb.loop_flag = 0;
}
}
/* build the VGMSTREAM */ /* build the VGMSTREAM */
vgmstream = allocate_vgmstream(fsb.channels,fsb.loop_flag); vgmstream = allocate_vgmstream(fsb.channels, fsb.loop_flag);
if (!vgmstream) goto fail; if (!vgmstream) goto fail;
vgmstream->sample_rate = fsb.sample_rate; vgmstream->sample_rate = fsb.sample_rate;
@ -343,43 +79,31 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
vgmstream->stream_size = fsb.stream_size; vgmstream->stream_size = fsb.stream_size;
vgmstream->meta_type = fsb.meta_type; vgmstream->meta_type = fsb.meta_type;
if (fsb.name_offset) if (fsb.name_offset)
read_string(vgmstream->stream_name,fsb.name_size+1, fsb.name_offset,sf); read_string(vgmstream->stream_name, fsb.name_size + 1, fsb.name_offset, sf);
switch(fsb.codec) { switch(fsb.codec) {
#ifdef VGM_USE_MPEG #ifdef VGM_USE_MPEG
case MPEG: { /* FSB4: Shatter (PS3), Way of the Samurai 3/4 (PS3) */ case MPEG: { /* FSB4: Shatter (PS3), Way of the Samurai 3/4 (PS3) */
mpeg_custom_config cfg = {0}; mpeg_custom_config cfg = {0};
cfg.fsb_padding = fsb.mpeg_padding; /* frames are sometimes padded for alignment */
cfg.fsb_padding = (vgmstream->channels > 2 ? 16 :
(fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED4 ? 4 :
(fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED ? 2 : 0)));
vgmstream->codec_data = init_mpeg_custom(sf, fsb.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_FSB, &cfg); vgmstream->codec_data = init_mpeg_custom(sf, fsb.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_FSB, &cfg);
if (!vgmstream->codec_data) goto fail; if (!vgmstream->codec_data) goto fail;
vgmstream->layout_type = layout_none; vgmstream->layout_type = layout_none;
//VGM_ASSERT(fsb.mode & FSOUND_MPEG_LAYER2, "FSB FSOUND_MPEG_LAYER2 found\n");/* not always set anyway */
VGM_ASSERT(fsb.mode & FSOUND_IGNORETAGS, "FSB FSOUND_IGNORETAGS found\n"); /* not seen */
break; break;
} }
#endif #endif
case XBOX_IMA: /* FSB3: Bioshock (PC), FSB4: Blade Kitten (PC) */
case IMA: /* FSB3: Bioshock (PC), FSB4: Blade Kitten (PC) */ case FSB_IMA: /* FSB3: Dead to Rights 2 (Xbox)-2ch, FSB4: Blade Kitten (PC)-6ch */
vgmstream->coding_type = coding_XBOX_IMA; vgmstream->coding_type = fsb.codec == FSB_IMA ? coding_FSB_IMA : coding_XBOX_IMA;
vgmstream->layout_type = layout_none; vgmstream->layout_type = layout_none;
/* "interleaved header" IMA, only used with >2ch (ex. Blade Kitten 6ch)
* or (seemingly) when flag is used (ex. Dead to Rights 2 (Xbox) 2ch in FSB3.1) */
if (vgmstream->channels > 2 || (fsb.mode & FSOUND_MULTICHANNEL))
vgmstream->coding_type = coding_FSB_IMA;
/* FSOUND_IMAADPCMSTEREO is "noninterleaved, true stereo IMA", but doesn't seem to be any different
* (found in FSB4: Shatter, Blade Kitten (PC), Hard Corps: Uprising (PS3)) */
break; break;
case PSX: /* FSB1: Jurassic Park Operation Genesis (PS2), FSB4: Spider Man Web of Shadows (PSP) */ case PSX: /* FSB1: Jurassic Park Operation Genesis (PS2), FSB4: Spider Man Web of Shadows (PSP) */
vgmstream->coding_type = coding_PSX; vgmstream->coding_type = coding_PSX;
vgmstream->layout_type = layout_interleave; vgmstream->layout_type = layout_interleave;
if (fsb.flags & FMOD_FSB_SOURCE_NOTINTERLEAVED) { if (fsb.non_interleaved) {
vgmstream->interleave_block_size = fsb.stream_size / fsb.channels; vgmstream->interleave_block_size = fsb.stream_size / fsb.channels;
} }
else { else {
@ -388,14 +112,14 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
break; break;
#ifdef VGM_USE_FFMPEG #ifdef VGM_USE_FFMPEG
case XMA: { /* FSB3: The Bourne Conspiracy 2008 (X360), FSB4: Armored Core V (X360), Hard Corps (X360) */ case XMA1: /* FSB3: The Bourne Conspiracy 2008 (X360), Forza Motorsport 23 (X360) */
int block_size = 0x8000; /* FSB default */ case XMA2: { /* FSB4: Armored Core V (X360), Hard Corps (X360) */
if (fsb.codec == XMA1) {
if (fsb.version != FMOD_FSB_VERSION_4_0) { /* 3.x, though no actual output changes [Guitar Hero III (X360), The Bourne Conspiracy (X360)] */
/* 3.x, though no actual output changes [ex. Guitar Hero III (X360), The Bourne Conspiracy (X360)] */
vgmstream->codec_data = init_ffmpeg_xma1_raw(sf, fsb.stream_offset, fsb.stream_size, fsb.channels, fsb.sample_rate, 0); vgmstream->codec_data = init_ffmpeg_xma1_raw(sf, fsb.stream_offset, fsb.stream_size, fsb.channels, fsb.sample_rate, 0);
} }
else { else {
int block_size = 0x8000; /* FSB default */
vgmstream->codec_data = init_ffmpeg_xma2_raw(sf, fsb.stream_offset, fsb.stream_size, fsb.num_samples, fsb.channels, fsb.sample_rate, block_size, 0); vgmstream->codec_data = init_ffmpeg_xma2_raw(sf, fsb.stream_offset, fsb.stream_size, fsb.num_samples, fsb.channels, fsb.sample_rate, block_size, 0);
} }
if (!vgmstream->codec_data) goto fail; if (!vgmstream->codec_data) goto fail;
@ -407,8 +131,8 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
} }
#endif #endif
case DSP: /* FSB3: Metroid Prime 3 (GC), FSB4: de Blob (Wii) */ case DSP: /* FSB3: Metroid Prime 3 (GC), FSB4: de Blob (Wii) */
if (fsb.flags & FMOD_FSB_SOURCE_NOTINTERLEAVED) { /* [de Blob (Wii) sfx)] */ if (fsb.non_interleaved) { /* [de Blob (Wii) sfx)] */
vgmstream->coding_type = coding_NGC_DSP; vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave; vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = fsb.stream_size / fsb.channels; vgmstream->interleave_block_size = fsb.stream_size / fsb.channels;
@ -422,8 +146,8 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
break; break;
#ifdef VGM_USE_CELT #ifdef VGM_USE_CELT
case CELT: { /* FSB4: War Thunder (PC), The Witcher 2 (PC), Vessel (PC) */ case CELT: { /* FSB4: War Thunder (PC), The Witcher 2 (PC), Vessel (PC) */
int is_new_lib; bool is_new_lib;
/* get libcelt version (set in the first subsong only, but try all extradata just in case) */ /* get libcelt version (set in the first subsong only, but try all extradata just in case) */
if (fsb.first_extradata_offset || fsb.extradata_offset) { if (fsb.first_extradata_offset || fsb.extradata_offset) {
@ -431,8 +155,8 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
read_u32le(fsb.first_extradata_offset, sf) : read_u32le(fsb.first_extradata_offset, sf) :
read_u32le(fsb.extradata_offset, sf); read_u32le(fsb.extradata_offset, sf);
switch(lib) { switch(lib) {
case 0x80000009: is_new_lib = 0; break; /* War Thunder (PC) */ case 0x80000009: is_new_lib = false; break; /* War Thunder (PC) */
case 0x80000010: is_new_lib = 1; break; /* Vessel (PC) */ case 0x80000010: is_new_lib = true; break; /* Vessel (PC) */
default: VGM_LOG("FSB: unknown CELT lib 0x%x\n", lib); goto fail; default: VGM_LOG("FSB: unknown CELT lib 0x%x\n", lib); goto fail;
} }
} }
@ -440,9 +164,10 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
/* split FSBs? try to guess from observed bitstreams */ /* split FSBs? try to guess from observed bitstreams */
uint16_t frame = read_u16be(fsb.stream_offset+0x04+0x04,sf); uint16_t frame = read_u16be(fsb.stream_offset+0x04+0x04,sf);
if ((frame & 0xF000) == 0x6000 || frame == 0xFFFE) { if ((frame & 0xF000) == 0x6000 || frame == 0xFFFE) {
is_new_lib = 1; is_new_lib = true;
} else { }
is_new_lib = 0; else {
is_new_lib = false;
} }
} }
@ -463,19 +188,18 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
} }
#endif #endif
case PCM8: /* assumed, no games known */ case PCM8: /* no games known */
vgmstream->coding_type = (fsb.mode & FSOUND_UNSIGNED) ? coding_PCM8_U : coding_PCM8; case PCM8U: /* FSB4: Crash Time 4: The Syndicate (X360) */
vgmstream->coding_type = (fsb.codec == PCM8U) ? coding_PCM8_U : coding_PCM8;
vgmstream->layout_type = layout_interleave; vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x1; vgmstream->interleave_block_size = 0x1;
break; break;
case PCM16: /* (PCM16) FSB4: Rocket Knight (PC), Another Century's Episode R (PS3), Toy Story 3 (Wii) */ case PCM16LE: /* FSB4: Rocket Knight (PC), Another Century's Episode R (PS3), Toy Story 3 (Wii) */
vgmstream->coding_type = (fsb.flags & FMOD_FSB_SOURCE_BIGENDIANPCM) ? coding_PCM16BE : coding_PCM16LE; case PCM16BE: /* FSB4: SpongeBob's Truth or Square (X360) */
vgmstream->coding_type = (fsb.codec == PCM16BE) ? coding_PCM16BE : coding_PCM16LE;
vgmstream->layout_type = layout_interleave; vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x2; vgmstream->interleave_block_size = 0x2;
/* sometimes FSOUND_MONO/FSOUND_STEREO is not set (ex. Dead Space iOS),
* or only STEREO/MONO but not FSOUND_8BITS/FSOUND_16BITS is set */
break; break;
default: default:
@ -483,7 +207,7 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
} }
if ( !vgmstream_open_stream(vgmstream, sf, fsb.stream_offset) ) if (!vgmstream_open_stream(vgmstream, sf, fsb.stream_offset))
goto fail; goto fail;
return vgmstream; return vgmstream;
@ -493,7 +217,7 @@ fail:
} }
#ifdef VGM_USE_CELT #ifdef VGM_USE_CELT
static layered_layout_data* build_layered_fsb_celt(STREAMFILE* sf, fsb_header* fsb, int is_new_lib) { static layered_layout_data* build_layered_fsb_celt(STREAMFILE* sf, fsb_header_t* fsb, bool is_new_lib) {
layered_layout_data* data = NULL; layered_layout_data* data = NULL;
STREAMFILE* temp_sf = NULL; STREAMFILE* temp_sf = NULL;
int i, layers = (fsb->channels+1) / 2; int i, layers = (fsb->channels+1) / 2;
@ -548,61 +272,348 @@ fail:
} }
#endif #endif
/* ****************************************** */
static STREAMFILE* setup_fsb4_wav_streamfile(STREAMFILE *streamfile, off_t subfile_offset, size_t subfile_size); /* FSB defines, copied from the public spec (https://www.fmod.org/questions/question/forum-4928/)
* for reference. The format is mostly compatible between FSB1/2/3/4, but not FSB5. */
/* FSB4 with "\0WAV" Header, found in Deadly Creatures (Wii). /* These flags are used for FMOD_FSB_HEADER::mode */
* Has a 0x10 BE header that holds the filesize (unsure if this is from a proper rip). */ #define FMOD_FSB_SOURCE_FORMAT 0x00000001 /* all samples stored in their original compressed format */
VGMSTREAM* init_vgmstream_fsb4_wav(STREAMFILE* sf) { #define FMOD_FSB_SOURCE_BASICHEADERS 0x00000002 /* samples should use the basic header structure */
VGMSTREAM* vgmstream = NULL; #define FMOD_FSB_SOURCE_ENCRYPTED 0x00000004 /* all sample data is encrypted */
STREAMFILE *test_sf = NULL; #define FMOD_FSB_SOURCE_BIGENDIANPCM 0x00000008 /* pcm samples have been written out in big-endian format */
off_t subfile_start = 0x10; #define FMOD_FSB_SOURCE_NOTINTERLEAVED 0x00000010 /* Sample data is not interleaved. */
size_t subfile_size = get_streamfile_size(sf) - 0x10 - 0x10; #define FMOD_FSB_SOURCE_MPEG_PADDED 0x00000020 /* Mpeg frames are now rounded up to the nearest 2 bytes for normal sounds, or 16 bytes for multichannel. */
#define FMOD_FSB_SOURCE_MPEG_PADDED4 0x00000040 /* Mpeg frames are now rounded up to the nearest 4 bytes for normal sounds, or 16 bytes for multichannel. */
/* check extensions */ /* These flags are used for FMOD_FSB_HEADER::version */
if ( !check_extensions(sf, "fsb,wii") ) #define FMOD_FSB_VERSION_3_0 0x00030000 /* FSB version 3.0 */
goto fail; #define FMOD_FSB_VERSION_3_1 0x00030001 /* FSB version 3.1 */
#define FMOD_FSB_VERSION_4_0 0x00040000 /* FSB version 4.0 */
if (read_32bitBE(0x00,sf) != 0x00574156) /* "\0WAV" */ /* FMOD 3 defines. These flags are used for FMOD_FSB_SAMPLE_HEADER::mode */
goto fail; #define FSOUND_LOOP_OFF 0x00000001 /* For non looping samples. */
#define FSOUND_LOOP_NORMAL 0x00000002 /* For forward looping samples. */
#define FSOUND_LOOP_BIDI 0x00000004 /* For bidirectional looping samples. (no effect if in hardware). */
#define FSOUND_8BITS 0x00000008 /* For 8 bit samples. */
#define FSOUND_16BITS 0x00000010 /* For 16 bit samples. */
#define FSOUND_MONO 0x00000020 /* For mono samples. */
#define FSOUND_STEREO 0x00000040 /* For stereo samples. */
#define FSOUND_UNSIGNED 0x00000080 /* For user created source data containing unsigned samples. */
#define FSOUND_SIGNED 0x00000100 /* For user created source data containing signed data. */
#define FSOUND_MPEG 0x00000200 /* For MPEG layer 2/3 data. */
#define FSOUND_CHANNELMODE_ALLMONO 0x00000400 /* Sample is a collection of mono channels. */
#define FSOUND_CHANNELMODE_ALLSTEREO 0x00000800 /* Sample is a collection of stereo channel pairs */
#define FSOUND_HW3D 0x00001000 /* Attempts to make samples use 3d hardware acceleration. (if the card supports it) */
#define FSOUND_2D 0x00002000 /* Tells software (not hardware) based sample not to be included in 3d processing. */
#define FSOUND_SYNCPOINTS_NONAMES 0x00004000 /* Specifies that syncpoints are present with no names */
#define FSOUND_DUPLICATE 0x00008000 /* This subsound is a duplicate of the previous one i.e. it uses the same sample data but w/different mode bits */
#define FSOUND_CHANNELMODE_PROTOOLS 0x00010000 /* Sample is 6ch and uses L C R LS RS LFE standard. */
#define FSOUND_MPEGACCURATE 0x00020000 /* For FSOUND_Stream_Open - for accurate FSOUND_Stream_GetLengthMs/FSOUND_Stream_SetTime. WARNING, see FSOUND_Stream_Open for inital opening time performance issues. */
#define FSOUND_HW2D 0x00080000 /* 2D hardware sounds. allows hardware specific effects */
#define FSOUND_3D 0x00100000 /* 3D software sounds */
#define FSOUND_32BITS 0x00200000 /* For 32 bit (float) samples. */
#define FSOUND_IMAADPCM 0x00400000 /* Contents are stored compressed as IMA ADPCM */
#define FSOUND_VAG 0x00800000 /* For PS2 only - Contents are compressed as Sony VAG format */
#define FSOUND_XMA 0x01000000 /* For Xbox360 only - Contents are compressed as XMA format */
#define FSOUND_GCADPCM 0x02000000 /* For Gamecube only - Contents are compressed as Gamecube DSP-ADPCM format */
#define FSOUND_MULTICHANNEL 0x04000000 /* For PS2 and Gamecube only - Contents are interleaved into a multi-channel (more than stereo) format */
#define FSOUND_OGG 0x08000000 /* For vorbis encoded ogg data */
#define FSOUND_CELT 0x08000000 /* For vorbis encoded ogg data */
#define FSOUND_MPEG_LAYER3 0x10000000 /* Data is in MP3 format. */
#define FSOUND_MPEG_LAYER2 0x00040000 /* Data is in MP2 format. */
#define FSOUND_LOADMEMORYIOP 0x20000000 /* For PS2 only - &quot;name&quot; will be interpreted as a pointer to data for streaming and samples. The address provided will be an IOP address */
#define FSOUND_IMAADPCMSTEREO 0x20000000 /* Signify IMA ADPCM is actually stereo not two interleaved mono */
#define FSOUND_IGNORETAGS 0x40000000 /* Skips id3v2 etc tag checks when opening a stream, to reduce seek/read overhead when opening files (helps with CD performance) */
#define FSOUND_SYNCPOINTS 0x80000000 /* Specifies that syncpoints are present */
/* parse FSB subfile */ /* These flags are used for FMOD_FSB_SAMPLE_HEADER::mode */
test_sf = setup_fsb4_wav_streamfile(sf, subfile_start,subfile_size); #define FSOUND_CHANNELMODE_MASK (FSOUND_CHANNELMODE_ALLMONO | FSOUND_CHANNELMODE_ALLSTEREO | FSOUND_CHANNELMODE_PROTOOLS)
if (!test_sf) goto fail; #define FSOUND_CHANNELMODE_DEFAULT 0x00000000 /* Determine channel assignment automatically from channel count. */
#define FSOUND_CHANNELMODE_RESERVED 0x00000C00
#define FSOUND_NORMAL (FSOUND_16BITS | FSOUND_SIGNED | FSOUND_MONO)
#define FSB_SAMPLE_DATA_ALIGN 32
vgmstream = init_vgmstream_fsb(test_sf);
if (!vgmstream) goto fail;
/* init the VGMSTREAM */ /* FSB loop info/flags are kind of wonky, try to make sense of them */
close_streamfile(test_sf); static void fix_loops(fsb_header_t* fsb) {
return vgmstream; /* ping-pong looping = no looping? (forward > reverse > forward) [Biker Mice from Mars (PS2)] */
VGM_ASSERT(fsb->mode & FSOUND_LOOP_BIDI, "FSB BIDI looping found\n");
VGM_ASSERT(fsb->mode & FSOUND_LOOP_OFF, "FSB LOOP OFF found\n"); /* sometimes used */
VGM_ASSERT(fsb->mode & FSOUND_LOOP_NORMAL, "FSB LOOP NORMAL found\n"); /* very rarely set */
fail: /* FMOD tool's default behaviour is creating files with full loops and no flags unless disabled
close_streamfile(test_sf); * manually via flag (can be overriden during program too), for all FSB versions. This makes jingles/sfx/voices
close_vgmstream(vgmstream); * loop when they shouldn't, but most music does full loops seamlessly, so we only want to disable
return NULL; * if it looks jingly enough. Incidentally, their tools can only make files with full loops. */
bool enable_loop, full_loop, is_small;
/* seems to mean forced loop */
enable_loop = (fsb->mode & FSOUND_LOOP_NORMAL);
/* for MPEG and CELT sometimes full loops are given with around/exact 1 frame less than num_samples,
* probably to account for encoder/decoder delay (ex. The Witcher 2, Hard Reset, Timeshift) */
if (fsb->codec == CELT)
full_loop = fsb->loop_start - 512 <= 0 && fsb->loop_end >= fsb->num_samples - 512; /* aproximate */
else if (fsb->codec == MPEG)
full_loop = fsb->loop_start - 1152 <= 0 && fsb->loop_end >= fsb->num_samples - 1152; /* WWF Legends of Wrestlemania uses 2 frames? */
else
full_loop = fsb->loop_start == 0 && fsb->loop_end == fsb->num_samples;
/* in seconds (lame but no better way) */
is_small = fsb->num_samples < 20 * fsb->sample_rate;
//;VGM_LOG("FSB: loop start=%i, loop end=%i, samples=%i, mode=%x\n", fsb->loop_start, fsb->loop_end, fsb->num_samples, fsb->mode);
//;VGM_LOG("FSB: enable=%i, full=%i, small=%i\n",enable_loop,full_loop,is_small );
fsb->loop_flag = !(fsb->mode & FSOUND_LOOP_OFF); /* disabled manually */
if (fsb->loop_flag && !enable_loop && full_loop && is_small) {
VGM_LOG("FSB: disabled unwanted loop\n");
fsb->loop_flag = false;
}
} }
static STREAMFILE* setup_fsb4_wav_streamfile(STREAMFILE* sf, off_t subfile_offset, size_t subfile_size) { /* covert codec info incomprehensibly defined as bitflags into single codec */
STREAMFILE *temp_sf = NULL, *new_sf = NULL; static void load_codec(fsb_header_t* fsb) {
if (fsb->mode & FSOUND_MPEG) fsb->codec = MPEG;
else if (fsb->mode & FSOUND_IMAADPCM) fsb->codec = XBOX_IMA;
else if (fsb->mode & FSOUND_VAG) fsb->codec = PSX;
else if (fsb->mode & FSOUND_XMA) fsb->codec = (fsb->version != FMOD_FSB_VERSION_4_0) ? XMA1 : XMA2;
else if (fsb->mode & FSOUND_GCADPCM) fsb->codec = DSP;
else if (fsb->mode & FSOUND_CELT) fsb->codec = CELT;
else if (fsb->mode & FSOUND_8BITS) fsb->codec = (fsb->mode & FSOUND_UNSIGNED) ? PCM8U : PCM8;
else fsb->codec = (fsb->flags & FMOD_FSB_SOURCE_BIGENDIANPCM) ? PCM16BE : PCM16LE;
/* setup subfile */ if (fsb->codec == MPEG) {
new_sf = open_wrap_streamfile(sf); //VGM_ASSERT(fsb->mode & FSOUND_MPEG_LAYER2, "FSB FSOUND_MPEG_LAYER2 found\n");/* not always set anyway */
if (!new_sf) goto fail; VGM_ASSERT(fsb->mode & FSOUND_IGNORETAGS, "FSB FSOUND_IGNORETAGS found\n"); /* mpeg only? not seen */
temp_sf = new_sf;
new_sf = open_clamp_streamfile(temp_sf, subfile_offset,subfile_size); fsb->mpeg_padding = (fsb->channels > 2 ? 0x10 :
if (!new_sf) goto fail; (fsb->flags & FMOD_FSB_SOURCE_MPEG_PADDED4 ? 0x04 :
temp_sf = new_sf; (fsb->flags & FMOD_FSB_SOURCE_MPEG_PADDED ? 0x02 : 0x000)));
}
new_sf = open_fakename_streamfile(temp_sf, NULL,"fsb"); if (fsb->codec == XBOX_IMA) {
if (!new_sf) goto fail; /* "interleaved header" IMA, only used with >2ch [Blade Kitten (PC)-6ch]
temp_sf = new_sf; * or (seemingly) when flag is used [Dead to Rights 2 (Xbox)- 2ch in FSB3.1] */
if (fsb->channels > 2 || (fsb->mode & FSOUND_MULTICHANNEL))
fsb->codec = FSB_IMA;
return temp_sf; /* FSOUND_IMAADPCMSTEREO is "noninterleaved, true stereo IMA", but doesn't seem to be any different
* (found in FSB4: Shatter, Blade Kitten (PC), Hard Corps: Uprising (PS3)) */
}
fail:
close_streamfile(temp_sf); /* officially only for PSX/DSP */
return NULL; if (fsb->codec == PSX || fsb->codec == DSP) {
fsb->non_interleaved = fsb->flags & FMOD_FSB_SOURCE_NOTINTERLEAVED;
}
/* PCM16: sometimes FSOUND_MONO/FSOUND_STEREO is not set [Dead Space (iOS)]
* or only STEREO/MONO but not FSOUND_8BITS/FSOUND_16BITS is set */
}
static bool parse_fsb(fsb_header_t* fsb, STREAMFILE* sf) {
int target_subsong = sf->stream_index;
fsb->id = read_u32be(0x00,sf);
if (fsb->id == get_id32be("FSB1")) {
fsb->meta_type = meta_FSB1;
fsb->base_header_size = 0x10;
fsb->sample_header_min = 0x40;
/* main header */
fsb->total_subsongs = read_s32le(0x04,sf);
fsb->sample_data_size = read_u32le(0x08,sf);
fsb->sample_headers_size = 0x40;
fsb->version = 0;
fsb->flags = 0;
if (fsb->total_subsongs > 1)
return false;
/* sample header (first stream only, not sure if there are multi-FSB1) */
{
off_t header_offset = fsb->base_header_size;
fsb->name_offset = header_offset;
fsb->name_size = 0x20;
fsb->num_samples = read_s32le(header_offset+0x20,sf);
fsb->stream_size = read_u32le(header_offset+0x24,sf);
fsb->sample_rate = read_s32le(header_offset+0x28,sf);
// 0x2c: ?
// 0x2e: ?
// 0x30: ?
// 0x32: ?
fsb->mode = read_u32le(header_offset+0x34,sf);
fsb->loop_start = read_s32le(header_offset+0x38,sf);
fsb->loop_end = read_s32le(header_offset+0x3c,sf);
fsb->channels = (fsb->mode & FSOUND_STEREO) ? 2 : 1;
if (fsb->loop_end > fsb->num_samples) /* this seems common... */
fsb->num_samples = fsb->loop_end;
/* DSP coefs, seek tables, etc */
fsb->extradata_offset = header_offset+fsb->sample_header_min;
fsb->stream_offset = fsb->base_header_size + fsb->sample_headers_size;
}
}
else {
if (fsb->id == get_id32be("FSB2")) {
fsb->meta_type = meta_FSB2;
fsb->base_header_size = 0x10;
fsb->sample_header_min = 0x40; /* guessed */
}
else if (fsb->id == get_id32be("FSB3")) {
fsb->meta_type = meta_FSB3;
fsb->base_header_size = 0x18;
fsb->sample_header_min = 0x40;
}
else if (fsb->id == get_id32be("FSB4")) {
fsb->meta_type = meta_FSB4;
fsb->base_header_size = 0x30;
fsb->sample_header_min = 0x50;
}
else {
goto fail;
}
/* main header */
fsb->total_subsongs = read_s32le(0x04,sf);
fsb->sample_headers_size = read_u32le(0x08,sf);
fsb->sample_data_size = read_u32le(0x0c,sf);
if (fsb->base_header_size > 0x10) {
fsb->version = read_u32le(0x10,sf);
fsb->flags = read_u32le(0x14,sf);
/* FSB4 only: */
// 0x18(8): hash
// 0x20(10): guid
} else {
fsb->version = 0;
fsb->flags = 0;
}
if (fsb->version == FMOD_FSB_VERSION_3_1) {
fsb->sample_header_min = 0x50;
}
else if (fsb->version != 0 /* FSB2 */
&& fsb->version != FMOD_FSB_VERSION_3_0
&& fsb->version != FMOD_FSB_VERSION_4_0) {
goto fail;
}
if (fsb->sample_headers_size < fsb->sample_header_min) goto fail;
if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > fsb->total_subsongs || fsb->total_subsongs < 1) goto fail;
/* sample header (N-stream) */
{
int i;
off_t header_offset = fsb->base_header_size;
off_t data_offset = fsb->base_header_size + fsb->sample_headers_size;
/* find target_stream header (variable sized) */
for (i = 0; i < fsb->total_subsongs; i++) {
uint32_t stream_header_size;
if ((fsb->flags & FMOD_FSB_SOURCE_BASICHEADERS) && i > 0) {
/* miniheader, all subsongs reuse first header (rare) [Biker Mice from Mars (PS2)] */
stream_header_size = 0x08;
fsb->num_samples = read_s32le(header_offset+0x00,sf);
fsb->stream_size = read_u32le(header_offset+0x04,sf);
fsb->loop_start = 0;
fsb->loop_end = 0;
/* XMA basic headers have extra data [Forza Motorsport 3 (X360)] */
if (fsb->mode & FSOUND_XMA) {
VGM_LOG("h=%x\n", (uint32_t)header_offset);
// 0x08: flags? (0x00=none?, 0x20=standard)
// 0x0c: sample related? (may be 0 with no seek table)
// 0x10: low number (may be 0 with no seek table)
uint32_t seek_size = read_u32le(header_offset+0x14, sf); /* may be 0 */
stream_header_size += 0x10 + seek_size;
/* seek table format: */
// 0x00: always 0x01?
// 0x04: seek entries? when 'flags' == 0x00 may be bigger than actual entries (flag means "no table" but space is still reserved?)
// per entry:
// 0x00: block offset
}
}
else {
/* subsong header for normal files */
stream_header_size = read_u16le(header_offset+0x00,sf);
fsb->name_offset = header_offset + 0x02;
fsb->name_size = 0x20 - 0x02;
fsb->num_samples = read_s32le(header_offset+0x20,sf);
fsb->stream_size = read_u32le(header_offset+0x24,sf);
fsb->loop_start = read_s32le(header_offset+0x28,sf);
fsb->loop_end = read_s32le(header_offset+0x2c,sf);
fsb->mode = read_u32le(header_offset+0x30,sf);
fsb->sample_rate = read_s32le(header_offset+0x34,sf);
// 0x38: defvol
// 0x3a: defpan
// 0x3c: defpri
fsb->channels = read_u16le(header_offset+0x3e,sf);
/* FSB3.1/4 only: */
// 0x40: mindistance
// 0x44: maxdistance
// 0x48: varfreq/size_32bits
// 0x4c: varvol
// 0x4e: fsb->varpan
/* DSP coefs, seek tables, etc */
if (stream_header_size > fsb->sample_header_min) {
fsb->extradata_offset = header_offset + fsb->sample_header_min;
if (fsb->first_extradata_offset == 0)
fsb->first_extradata_offset = fsb->extradata_offset;
}
}
if (i + 1 == target_subsong) /* final data_offset found */
break;
/* there are no offsets so add manually for next subsong */
header_offset += stream_header_size;
data_offset += fsb->stream_size;
/* some subsongs offsets need padding (most FSOUND_IMAADPCM, few MPEG too [Hard Reset (PC) subsong 5])
* other codecs may set PADDED4 (ex. XMA) but don't seem to need it and work fine */
if (fsb->flags & FMOD_FSB_SOURCE_MPEG_PADDED4) {
if (data_offset % 0x20)
data_offset += 0x20 - (data_offset % 0x20);
}
}
/* target not found */
if (i > fsb->total_subsongs)
goto fail;
fsb->stream_offset = data_offset;
}
}
load_codec(fsb);
/* correct compared to FMOD's tools */
if (fsb->loop_end)
fsb->loop_end += 1;
fix_loops(fsb);
/* sometimes there is garbage at the end or missing bytes due to improper ripping (maybe should reject them...) */
vgm_asserti(fsb->base_header_size + fsb->sample_headers_size + fsb->sample_data_size != get_streamfile_size(sf),
"FSB wrong head/data_size found (expected 0x%x vs 0x%x)\n",
fsb->base_header_size + fsb->sample_headers_size + fsb->sample_data_size, (uint32_t)get_streamfile_size(sf));
/* XOR encryption for some FSB4, though the flag is only seen after decrypting */
//;VGM_ASSERT(fsb->flags & FMOD_FSB_SOURCE_ENCRYPTED, "FSB ENCRYPTED found\n");
return true;
fail:
return false;
} }

View File

@ -12,14 +12,14 @@ VGMSTREAM* init_vgmstream_fsb_encrypted(STREAMFILE* sf) {
/* ignore non-encrypted FSB */ /* ignore non-encrypted FSB */
if ((read_u32be(0x00,sf) & 0xFFFFFF00) == get_id32be("FSB\0")) if ((read_u32be(0x00,sf) & 0xFFFFFF00) == get_id32be("FSB\0"))
goto fail; return NULL;
/* checks */ /* checks */
/* .fsb: standard /* .fsb: standard
* .fsb.ps3: various Guitar Hero (PS3) * .fsb.ps3: various Guitar Hero (PS3)
* .fsb.xen: various Guitar Hero (X360/PC) */ * .fsb.xen: various Guitar Hero (X360/PC) */
if (!check_extensions(sf, "fsb,ps3,xen")) if (!check_extensions(sf, "fsb,ps3,xen"))
goto fail; return NULL;
/* try fsbkey + all combinations of FSB4/5 and decryption algorithms */ /* try fsbkey + all combinations of FSB4/5 and decryption algorithms */
{ {
@ -27,7 +27,7 @@ VGMSTREAM* init_vgmstream_fsb_encrypted(STREAMFILE* sf) {
size_t key_size = read_key_file(key, FSB_KEY_MAX, sf); size_t key_size = read_key_file(key, FSB_KEY_MAX, sf);
if (key_size) { if (key_size) {
vgmstream = test_fsbkey(sf, key, key_size, MODE_FSBS_ALL); vgmstream = test_fsbkey(sf, key, key_size, MODE_FSBS);
return vgmstream; return vgmstream;
} }
} }
@ -60,10 +60,10 @@ static VGMSTREAM* test_fsbkey(STREAMFILE* sf, const uint8_t* key, size_t key_siz
if (!key_size) if (!key_size)
return NULL; return NULL;
int test_fsb4 = flags & FLAG_FSB4; bool test_fsb4 = flags & FLAG_FSB4;
int test_fsb5 = flags & FLAG_FSB5; bool test_fsb5 = flags & FLAG_FSB5;
int test_std = flags & FLAG_STD; bool test_alt = flags & FLAG_ALT;
int test_alt = flags & FLAG_ALT; bool test_std = flags & FLAG_STD;
if (!vc && test_std && test_fsb_streamfile(sf, key, key_size, 0)) { if (!vc && test_std && test_fsb_streamfile(sf, key, key_size, 0)) {
temp_sf = setup_fsb_streamfile(sf, key, key_size, 0); temp_sf = setup_fsb_streamfile(sf, key, key_size, 0);

View File

@ -3,85 +3,77 @@
#include <stdint.h> #include <stdint.h>
/* /*
* List of known keys, found in aluigi's site (http://aluigi.altervista.org), forums, guessfsb.exe or manually. * List of known keys, some found in aluigi's site (http://aluigi.altervista.org), forums, guessfsb.exe or manually.
*/ */
// Unknown:
// - Battle: Los Angeles
// - Guitar Hero: Warriors of Rock, DJ hero FSB
// - Longmenkezhan
// - Gas Guzzlers: Combat Carnage (PC?) "C5FA83EA64B34EC2BFE" hex or text? [FSB5]
typedef struct { typedef struct {
uint8_t flags; uint8_t flags;
const char* key; const char* key;
size_t key_size; /* precalc'd for speed */ size_t key_size; /* precalc'd for speed */
} fsbkey_info; } fsbkey_info;
#define FLAG_FSB4 (1 << 0) /* key is valid for FSB4/3 */ #define FLAG_FSB4 (1 << 0) /* key is valid for FSB3/4 */
#define FLAG_FSB5 (1 << 1) /* key is valid for FSB5 */ #define FLAG_FSB5 (1 << 1) /* key is valid for FSB5 */
#define FLAG_STD (1 << 2) /* regular XOR mode */ #define FLAG_STD (1 << 2) /* regular XOR mode */
#define FLAG_ALT (1 << 3) /* alt XOR mode (seemingly older files or possibly FSB3 only) */ #define FLAG_ALT (1 << 3) /* alt XOR mode (seemingly older files or possibly FSB3 only) */
#define MODE_FSB4_STD (FLAG_FSB4 | FLAG_STD) #define MODE_FSB3 (FLAG_FSB4 | FLAG_ALT)
#define MODE_FSB4_ALT (FLAG_FSB4 | FLAG_ALT) #define MODE_FSB4 (FLAG_FSB4 | FLAG_STD)
#define MODE_FSB4_ALL (FLAG_FSB4 | FLAG_STD | FLAG_ALT) #define MODE_FSB5 (FLAG_FSB5 | FLAG_STD)
#define MODE_FSB5_STD (FLAG_FSB5 | FLAG_STD) #define MODE_FSBS (FLAG_FSB4 | FLAG_FSB5 | FLAG_STD | FLAG_ALT)
#define MODE_FSB5_ALT (FLAG_FSB5 | FLAG_STD)
#define MODE_FSB5_ALL (FLAG_FSB5 | FLAG_STD | FLAG_ALT)
#define MODE_FSBS_STD (FLAG_FSB4 | FLAG_FSB5 | FLAG_STD)
#define MODE_FSBS_ALL (FLAG_FSB4 | FLAG_FSB5 | FLAG_STD | FLAG_ALT)
/* ugly macro for string + precomputed len (removing string's extra NULL)*/ /* ugly macro for string + precomputed len (removing string's extra NULL)*/
#define FSBKEY_ADD(key) key, sizeof(key) - 1 #define FSBKEY_ADD(key) key, sizeof(key) - 1
static const fsbkey_info fsbkey_list[] = { static const fsbkey_info fsbkey_list[] = {
{ MODE_FSBS_STD, FSBKEY_ADD("DFm3t4lFTW") }, // Double Fine Productions: Brutal Legend, Massive Chalice, etc (multi) { MODE_FSB4, FSBKEY_ADD("DFm3t4lFTW") }, // Double Fine Productions: Brutal Legend, Massive Chalice, etc (multi)
{ MODE_FSB4_STD, FSBKEY_ADD("nos71RiT") }, // DJ Hero 2 (X360) { MODE_FSB5, FSBKEY_ADD("DFm3t4lFTW") }, // Double Fine Productions
{ MODE_FSB5_STD, FSBKEY_ADD("H$#FJa%7gRZZOlxLiN50&g5Q") }, // N++ (PC?) { MODE_FSB4, FSBKEY_ADD("nos71RiT") }, // DJ Hero 2 (X360)
{ MODE_FSB5_STD, FSBKEY_ADD("sTOoeJXI2LjK8jBMOk8h5IDRNZl3jq3I") }, // Slightly Mad Studios: Project CARS (PC?), World of Speed (PC) { MODE_FSB5, FSBKEY_ADD("H$#FJa%7gRZZOlxLiN50&g5Q") }, // N++ (PC?)
{ MODE_FSB5_STD, FSBKEY_ADD("%lAn2{Pi*Lhw3T}@7*!kV=?qS$@iNlJ") }, // Ghost in the Shell: First Assault (PC) { MODE_FSB5, FSBKEY_ADD("sTOoeJXI2LjK8jBMOk8h5IDRNZl3jq3I") }, // Slightly Mad Studios: Project CARS (PC?), World of Speed (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("1^7%82#&5$~/8sz") }, // RevHeadz Engine Sounds (Mobile) { MODE_FSB5, FSBKEY_ADD("%lAn2{Pi*Lhw3T}@7*!kV=?qS$@iNlJ") }, // Ghost in the Shell: First Assault (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("FDPrVuT4fAFvdHJYAgyMzRF4EcBAnKg") }, // Dark Souls 3 (PC) { MODE_FSB5, FSBKEY_ADD("1^7%82#&5$~/8sz") }, // RevHeadz Engine Sounds (Mobile)
{ MODE_FSB4_STD, FSBKEY_ADD("p&oACY^c4LK5C2v^x5nIO6kg5vNH$tlj") }, // Need for Speed Shift 2 Unleashed (PC) { MODE_FSB5, FSBKEY_ADD("FDPrVuT4fAFvdHJYAgyMzRF4EcBAnKg") }, // Dark Souls 3 (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("996164B5FC0F402983F61F220BB51DC6") }, // Mortal Kombat X/XL (PC) { MODE_FSB4, FSBKEY_ADD("p&oACY^c4LK5C2v^x5nIO6kg5vNH$tlj") }, // Need for Speed Shift 2 Unleashed (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("logicsounddesignmwsdev") }, // Mirror War: Reincarnation of Holiness (PC) { MODE_FSB5, FSBKEY_ADD("996164B5FC0F402983F61F220BB51DC6") }, // Mortal Kombat X/XL (PC)
{ MODE_FSBS_ALL, FSBKEY_ADD("gat@tcqs2010") }, // Xian Xia Chuan (PC) [untested] { MODE_FSB5, FSBKEY_ADD("logicsounddesignmwsdev") }, // Mirror War: Reincarnation of Holiness (PC)
{ MODE_FSBS_ALL, FSBKEY_ADD("j1$Mk0Libg3#apEr42mo") }, // Critter Crunch (PC), Superbrothers: Sword & Sworcery (PC) [untested] { MODE_FSBS, FSBKEY_ADD("gat@tcqs2010") }, // Xian Xia Chuan (PC) [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("@kdj43nKDN^k*kj3ndf02hd95nsl(NJG") }, // Cyphers [untested] { MODE_FSBS, FSBKEY_ADD("j1$Mk0Libg3#apEr42mo") }, // Critter Crunch (PC), Superbrothers: Sword & Sworcery (PC) [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("Xiayuwu69252.Sonicli81223#$*@*0") }, // Xuan Dou Zhi Wang / King of Combat [untested] { MODE_FSBS, FSBKEY_ADD("@kdj43nKDN^k*kj3ndf02hd95nsl(NJG") }, // Cyphers [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("kri_tika_5050_") }, // Ji Feng Zhi Ren / Kritika Online [untested] { MODE_FSBS, FSBKEY_ADD("Xiayuwu69252.Sonicli81223#$*@*0") }, // Xuan Dou Zhi Wang / King of Combat [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("mint78run52") }, // Invisible Inc. (PC?) [untested] { MODE_FSBS, FSBKEY_ADD("kri_tika_5050_") }, // Ji Feng Zhi Ren / Kritika Online [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("5atu6w4zaw") }, // Guitar Hero 3 [untested] { MODE_FSBS, FSBKEY_ADD("mint78run52") }, // Invisible Inc. (PC?) [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("B2A7BB00") }, // Supreme Commander 2 [untested] { MODE_FSBS, FSBKEY_ADD("5atu6w4zaw") }, // Guitar Hero 3 [untested]
{ MODE_FSB4_STD, FSBKEY_ADD("ghfxhslrghfxhslr") }, // Cookie Run: Ovenbreak { MODE_FSB4, FSBKEY_ADD("B2A7BB00") }, // Supreme Commander 2 (PC)
{ MODE_FSB4_ALT, FSBKEY_ADD("truck/impact/carbody") }, // Monster Jam (PS2) [FSB3] { MODE_FSB4, FSBKEY_ADD("ghfxhslrghfxhslr") }, // Cookie Run: Ovenbreak
{ MODE_FSB4_ALT, FSBKEY_ADD("\xFC\xF9\xE4\xB3\xF5\x57\x5C\xA5\xAC\x13\xEC\x4A\x43\x19\x58\xEB\x4E\xF3\x84\x0B\x8B\x78\xFA\xFD\xBB\x18\x46\x7E\x31\xFB\xD0") }, // Guitar Hero 5 (X360) { MODE_FSB3, FSBKEY_ADD("truck/impact/carbody") }, // Monster Jam (PS2) [FSB3]
{ MODE_FSB5_STD, FSBKEY_ADD("G0KTrWjS9syqF7vVD6RaVXlFD91gMgkC") }, // Sekiro: Shadows Die Twice (PC) { MODE_FSB5, FSBKEY_ADD("G0KTrWjS9syqF7vVD6RaVXlFD91gMgkC") }, // Sekiro: Shadows Die Twice (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("BasicEncryptionKey") }, // SCP: Unity (PC) { MODE_FSB5, FSBKEY_ADD("BasicEncryptionKey") }, // SCP: Unity (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("FXnTffGJ9LS855Gc") }, // Worms Rumble Beta (PC) { MODE_FSB5, FSBKEY_ADD("FXnTffGJ9LS855Gc") }, // Worms Rumble Beta (PC)
{ MODE_FSB4_STD, FSBKEY_ADD("qjvkeoqkrdhkdckd") }, // Bubble Fighter (PC) { MODE_FSB4, FSBKEY_ADD("qjvkeoqkrdhkdckd") }, // Bubble Fighter (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("p@4_ih*srN:UJk&8") }, // Fall Guys (PC) update ~2021-11 { MODE_FSB5, FSBKEY_ADD("p@4_ih*srN:UJk&8") }, // Fall Guys (PC) update ~2021-11
{ MODE_FSB5_STD, FSBKEY_ADD(",&.XZ8]fLu%caPF+") }, // Fall Guys (PC) update ~2022-07 { MODE_FSB5, FSBKEY_ADD(",&.XZ8]fLu%caPF+") }, // Fall Guys (PC) update ~2022-07
{ MODE_FSB5_STD, FSBKEY_ADD("^*4[hE>K]x90Vj") }, // Fall Guys (PC) update ~2023-05 { MODE_FSB5, FSBKEY_ADD("^*4[hE>K]x90Vj") }, // Fall Guys (PC) update ~2023-05
{ MODE_FSB5_STD, FSBKEY_ADD("Achilles_0_15_DpG") }, // Achilles: Legends Untold (PC) { MODE_FSB5, FSBKEY_ADD("Achilles_0_15_DpG") }, // Achilles: Legends Untold (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("4FB8CC894515617939F4E1B7D50972D27213B8E6") }, // Cult of the Lamb Demo (PC) { MODE_FSB5, FSBKEY_ADD("4FB8CC894515617939F4E1B7D50972D27213B8E6") }, // Cult of the Lamb Demo (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("X3EK%Bbga-%Y9HZZ%gkc*C512*$$DhRxWTGgjUG@=rUD") }, // Signalis (PC) { MODE_FSB5, FSBKEY_ADD("X3EK%Bbga-%Y9HZZ%gkc*C512*$$DhRxWTGgjUG@=rUD") }, // Signalis (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("281ad163160cfc16f9a22c6755a64fad") }, // Ash Echoes beta (Android) { MODE_FSB5, FSBKEY_ADD("281ad163160cfc16f9a22c6755a64fad") }, // Ash Echoes beta (Android)
{ MODE_FSB5_STD, FSBKEY_ADD("Aurogon666") }, // Afterimage demo (PC) { MODE_FSB5, FSBKEY_ADD("Aurogon666") }, // Afterimage demo (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("IfYouLikeThosesSoundsWhyNotRenumerateTheir2Authors?") }, // Blanc (PC/Switch) { MODE_FSB5, FSBKEY_ADD("IfYouLikeThosesSoundsWhyNotRenumerateTheir2Authors?") }, // Blanc (PC/Switch)
{ MODE_FSB5_STD, FSBKEY_ADD("L36nshM520") }, // Nishuihan Mobile (Android) { MODE_FSB5, FSBKEY_ADD("L36nshM520") }, // Nishuihan Mobile (Android)
{ MODE_FSB5_STD, FSBKEY_ADD("Forza2!") }, // Forza Motorsport (PC) { MODE_FSB5, FSBKEY_ADD("Forza2!") }, // Forza Motorsport (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("cbfjZTlUPaZI") }, // JDM: Japanese Drift Master (PC) { MODE_FSB5, FSBKEY_ADD("cbfjZTlUPaZI") }, // JDM: Japanese Drift Master (PC)
{ MODE_FSB4_ALT, FSBKEY_ADD("tkdnsem000") }, // Ys Online: The Call of Solum (PC) [FSB3] (alt key: 2ED62676CEA6B60C0C0C) { MODE_FSB3, FSBKEY_ADD("tkdnsem000") }, // Ys Online: The Call of Solum (PC) [FSB3] (alt key: 2ED62676CEA6B60C0C0C)
{ MODE_FSB4_STD, FSBKEY_ADD("4DxgpNV3pQLPD6GT7g9Gf6eWU7SXutGQ") }, // Test Drive: Ferrari Racing Legends (PC) { MODE_FSB4, FSBKEY_ADD("4DxgpNV3pQLPD6GT7g9Gf6eWU7SXutGQ") }, // Test Drive: Ferrari Racing Legends (PC)
{ MODE_FSB4_STD, FSBKEY_ADD("AjaxIsTheGoodestBoy") }, // Hello Kitty: Island Adventure (iOS) { MODE_FSB4, FSBKEY_ADD("AjaxIsTheGoodestBoy") }, // Hello Kitty: Island Adventure (iOS)
{ MODE_FSB5_STD, FSBKEY_ADD("resoforce") }, // Rivals of Aether 2 (PC) { MODE_FSB5, FSBKEY_ADD("resoforce") }, // Rivals of Aether 2 (PC)
{ MODE_FSB5, FSBKEY_ADD("3cfe772db5b55b806541d3faf894020e") }, // Final Fantasy XV: War for Eos (Android)
/* these games use a key per file, generated from the filename; could be possible to add them but there is a lot of songs, /* some games use a key per file, generated from the filename
so external .fsbkey may be better (use guessfsb 3.1 with --write-key-file or ) */ * (could add all of them but there are a lot of songs, so external .fsbkey are probably better) */
//{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero: Metallica (PC/PS3/X360) [FSB4] //{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero: Metallica (PC/PS3/X360) [FSB4]
//{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero: World Tour (PC/PS3/X360) [FSB4] //{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero: World Tour (PC/PS3/X360) [FSB4]
//{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero 5 (PC/PS3/X360) [FSB4] (streams seem to use the same default key)
}; };
static const int fsbkey_list_count = sizeof(fsbkey_list) / sizeof(fsbkey_list[0]); static const int fsbkey_list_count = sizeof(fsbkey_list) / sizeof(fsbkey_list[0]);

View File

@ -1314,9 +1314,9 @@ static const hcakey_info hcakey_list[] = {
// Sonic Rumble (Android) // Sonic Rumble (Android)
{6834182874188563}, // 001847A7328BCB13 {6834182874188563}, // 001847A7328BCB13
// P A Certain Magical Index 2 (Android) // P A Certain Magical Index 1/2 (Android)
{5963}, // 000000000000174B {5963}, // 000000000000174B
// Reynatis (Switch) // Reynatis (Switch)
{5963496778882477625}, // 52C298B97479EE39 {5963496778882477625}, // 52C298B97479EE39

View File

@ -208,10 +208,9 @@ VGMSTREAM * init_vgmstream_aus(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_rws(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_rws(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_fsb(STREAMFILE * streamFile); VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_fsb4_wav(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_fsb5(STREAMFILE * streamFile); VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_rwax(STREAMFILE* sf); VGMSTREAM* init_vgmstream_rwax(STREAMFILE* sf);

View File

@ -73,6 +73,8 @@ void vgm_log_set_callback(void* ctx_p, int level, int type, void* callback);
printf("\n"); \ printf("\n"); \
} while (0) } while (0)
#define VGM_STEP() do { printf("%s: %s:%i\n", __FILE__, __FUNCTION__, __LINE__); } while (0)
#else /* VGM_DEBUG_OUTPUT */ #else /* VGM_DEBUG_OUTPUT */
#define VGM_LOG_ONCE(...) /* nothing */ #define VGM_LOG_ONCE(...) /* nothing */
@ -83,6 +85,8 @@ void vgm_log_set_callback(void* ctx_p, int level, int type, void* callback);
#define VGM_LOGB(buf, buf_size, bytes_per_line) /* nothing */ #define VGM_LOGB(buf, buf_size, bytes_per_line) /* nothing */
#define VGM_STEP() /* nothing */
#endif /*VGM_DEBUG_OUTPUT*/ #endif /*VGM_DEBUG_OUTPUT*/
#endif #endif

View File

@ -79,7 +79,6 @@ init_vgmstream_t init_vgmstream_functions[] = {
init_vgmstream_aus, init_vgmstream_aus,
init_vgmstream_rws, init_vgmstream_rws,
init_vgmstream_fsb, init_vgmstream_fsb,
init_vgmstream_fsb4_wav,
init_vgmstream_fsb5, init_vgmstream_fsb5,
init_vgmstream_rwax, init_vgmstream_rwax,
init_vgmstream_xwb, init_vgmstream_xwb,