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
$(MAKE) -C cli vgmstream123
api_example: version
$(MAKE) -C cli api_example
winamp: version
$(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)
TARGET_EXT_LIBS =
ifeq ($(TARGET_OS),Windows_NT)
OUTPUT_CLI = vgmstream-cli.exe
OUTPUT_123 = vgmstream123.exe
OUTPUT_CLI = vgmstream-cli
OUTPUT_123 = vgmstream123
OUTPUT_API = api_example
ifeq ($(TARGET_OS),Windows_NT)
CFLAGS += -DWIN32 -I../ext_includes -I../ext_libs/Getopt
LDFLAGS += -L../ext_libs/$(DLL_DIR)
LIBAO_INC = -I$(LIBAO_IPATH)
LIBAO_LIB = -L$(LIBAO_LPATH) -lao
else
OUTPUT_CLI = vgmstream-cli
OUTPUT_123 = vgmstream123
#todo move to subfolders and remove
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)
$(STRIP) $(OUTPUT_123)
api_example: libvgmstream.a $(TARGET_EXT_LIBS)
$(CC) $(CFLAGS) api_example.c $(LDFLAGS) -o $(OUTPUT_API)
$(STRIP) api_example
libvgmstream.a:
$(MAKE) -C ../src $@
@ -57,6 +59,6 @@ $(TARGET_EXT_LIBS):
$(MAKE) -C ../ext_libs $@
clean:
$(RMF) $(OUTPUT_CLI) $(OUTPUT_123)
$(RMF) $(OUTPUT_CLI) $(OUTPUT_123) $(OUTPUT_123)
.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);
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 */
}
@ -930,7 +930,7 @@ static int write_file(VGMSTREAM* vgmstream, cli_config* cfg) {
if (!cfg->decode_only) {
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
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.
## 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.
### 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:
- `--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
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
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:
- adding `__declspec(dllexport)` to a function (usually done with `#define EXPORT ...` and similar ways)
- 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).

136
src/api.h
View File

@ -1,26 +1,29 @@
#ifndef _API_H_
#define _API_H_
#include "base/plugins.h" //TODO: to be removed
#include "base/plugins.h"
//possible future public/opaque API
#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 "api_streamfile.h"
#include <stdbool.h>
/* Current API version (major=breaking API/ABI changes, minor=compatible ABI changes).
* 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) */
/* standard C param call and name mangling (to avoid __stdcall / .defs) */
//#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)
#define LIBVGMSTREAM_API __declspec(dllexport) /* when exporting/creating vgmstream DLL */
#elif defined(LIBVGMSTREAM_IMPORT)
@ -29,104 +32,17 @@
#define LIBVGMSTREAM_API /* nothing, internal/default */
#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

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;
}
/* 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) {
int errcode = 0;
/* basic IO/format setup */
/* custom IO/format setup */
data->buffer = av_malloc(FFMPEG_DEFAULT_IO_BUFFER_SIZE);
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? */
/* on reset could use AVFormatContext.iformat to reload old format too */
/* format detection */
errcode = avformat_open_input(&data->formatCtx, NULL /*""*/, NULL, NULL);
if (errcode < 0) goto fail;
errcode = avformat_find_stream_info(data->formatCtx, NULL);
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;
@ -485,24 +502,22 @@ static int init_ffmpeg_config(ffmpeg_codec_data* data, int target_subsong, int r
data->stream_count = stream_count;
}
/* setup codec with stream info */
/* setup codec from stream info */
data->codecCtx = avcodec_alloc_context3(NULL);
if (!data->codecCtx) goto fail;
errcode = avcodec_parameters_to_context(data->codecCtx, data->formatCtx->streams[data->stream_index]->codecpar);
if (errcode < 0) goto fail;
/* deprecated and seemingly not needed */
//av_codec_set_pkt_timebase(data->codecCtx, stream->time_base);
//av_codec_set_pkt_timebase(data->codecCtx, stream->time_base); /* deprecated and seemingly not needed */
/* not useddeprecated and seemingly not needed */
data->codec = avcodec_find_decoder(data->codecCtx->codec_id);
if (!data->codec) goto fail;
errcode = avcodec_open2(data->codecCtx, data->codec, NULL);
if (errcode < 0) goto fail;
/* prepare codec and frame/packet buffers */
/* prepare frame/packet buffers */
data->packet = av_malloc(sizeof(AVPacket)); /* av_packet_alloc? */
if (!data->packet) goto fail;
av_new_packet(data->packet, 0);

View File

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

View File

@ -4,86 +4,23 @@
#include "fsb_interleave_streamfile.h"
/* ************************************************************************************************
* 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 enum { MPEG, XBOX_IMA, FSB_IMA, PSX, XMA1, XMA2, DSP, CELT, PCM8, PCM8U, PCM16LE, PCM16BE } fsb_codec_t;
typedef struct {
/* main header */
uint32_t id;
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 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 version; /* extended fsb version (in FSB 3/3.1/4) */
uint32_t flags; /* flags common to all streams (in FSB 3/3.1/4) */
/* sample header */
uint32_t num_samples;
uint32_t stream_size;
uint32_t loop_start;
uint32_t loop_end;
uint32_t mode;
int32_t num_samples;
int32_t loop_start;
int32_t loop_end;
int32_t sample_rate;
uint16_t channels;
uint32_t mode; /* flags for current stream */
/* extra */
uint32_t base_header_size;
uint32_t sample_header_min;
@ -94,245 +31,44 @@ typedef struct {
off_t name_offset;
size_t name_size;
int loop_flag;
int mpeg_padding;
bool non_interleaved;
bool loop_flag;
off_t stream_offset;
fsb_codec_t codec;
} fsb_header;
} fsb_header_t;
/* ********************************************************************************** */
static layered_layout_data* build_layered_fsb_celt(STREAMFILE* sf, fsb_header* fsb, int is_new_lib);
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);
/* FSB1~4 - from games using FMOD audio middleware */
VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
int target_subsong = sf->stream_index;
fsb_header fsb = {0};
fsb_header_t fsb = {0};
/* checks */
if ((read_u32be(0x00,sf) & 0xFFFFFF00) != get_id32be("FSB\0"))
goto fail;
uint32_t id = read_u32be(0x00,sf);
if (id < get_id32be("FSB1") || id > get_id32be("FSB4"))
return NULL;
/* .fsb: standard
* .bnk: Hard Corps Uprising (PS3)
* .sfx: Geon Cube (Wii)
* .ps3: Neversoft games (PS3)
* .xen: Neversoft games (X360/PC) */
if ( !check_extensions(sf, "fsb,bnk,sfx,ps3,xen") )
goto fail;
if (!check_extensions(sf, "fsb,bnk,sfx,ps3,xen"))
return NULL;
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_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;
}
}
if (!parse_fsb(&fsb, sf))
return NULL;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(fsb.channels,fsb.loop_flag);
vgmstream = allocate_vgmstream(fsb.channels, fsb.loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = fsb.sample_rate;
@ -343,43 +79,31 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
vgmstream->stream_size = fsb.stream_size;
vgmstream->meta_type = fsb.meta_type;
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) {
#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};
cfg.fsb_padding = (vgmstream->channels > 2 ? 16 :
(fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED4 ? 4 :
(fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED ? 2 : 0)));
cfg.fsb_padding = fsb.mpeg_padding; /* frames are sometimes padded for alignment */
vgmstream->codec_data = init_mpeg_custom(sf, fsb.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_FSB, &cfg);
if (!vgmstream->codec_data) goto fail;
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;
}
#endif
case IMA: /* FSB3: Bioshock (PC), FSB4: Blade Kitten (PC) */
vgmstream->coding_type = coding_XBOX_IMA;
case XBOX_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 = fsb.codec == FSB_IMA ? coding_FSB_IMA : coding_XBOX_IMA;
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;
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->layout_type = layout_interleave;
if (fsb.flags & FMOD_FSB_SOURCE_NOTINTERLEAVED) {
if (fsb.non_interleaved) {
vgmstream->interleave_block_size = fsb.stream_size / fsb.channels;
}
else {
@ -388,14 +112,14 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
break;
#ifdef VGM_USE_FFMPEG
case XMA: { /* FSB3: The Bourne Conspiracy 2008 (X360), FSB4: Armored Core V (X360), Hard Corps (X360) */
int block_size = 0x8000; /* FSB default */
if (fsb.version != FMOD_FSB_VERSION_4_0) {
/* 3.x, though no actual output changes [ex. Guitar Hero III (X360), The Bourne Conspiracy (X360)] */
case XMA1: /* FSB3: The Bourne Conspiracy 2008 (X360), Forza Motorsport 23 (X360) */
case XMA2: { /* FSB4: Armored Core V (X360), Hard Corps (X360) */
if (fsb.codec == XMA1) {
/* 3.x, though no actual output changes [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);
}
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);
}
if (!vgmstream->codec_data) goto fail;
@ -407,8 +131,8 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
}
#endif
case DSP: /* FSB3: Metroid Prime 3 (GC), FSB4: de Blob (Wii) */
if (fsb.flags & FMOD_FSB_SOURCE_NOTINTERLEAVED) { /* [de Blob (Wii) sfx)] */
case DSP: /* FSB3: Metroid Prime 3 (GC), FSB4: de Blob (Wii) */
if (fsb.non_interleaved) { /* [de Blob (Wii) sfx)] */
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = fsb.stream_size / fsb.channels;
@ -422,8 +146,8 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
break;
#ifdef VGM_USE_CELT
case CELT: { /* FSB4: War Thunder (PC), The Witcher 2 (PC), Vessel (PC) */
int is_new_lib;
case CELT: { /* FSB4: War Thunder (PC), The Witcher 2 (PC), Vessel (PC) */
bool is_new_lib;
/* get libcelt version (set in the first subsong only, but try all extradata just in case) */
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.extradata_offset, sf);
switch(lib) {
case 0x80000009: is_new_lib = 0; break; /* War Thunder (PC) */
case 0x80000010: is_new_lib = 1; break; /* Vessel (PC) */
case 0x80000009: is_new_lib = false; break; /* War Thunder (PC) */
case 0x80000010: is_new_lib = true; break; /* Vessel (PC) */
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 */
uint16_t frame = read_u16be(fsb.stream_offset+0x04+0x04,sf);
if ((frame & 0xF000) == 0x6000 || frame == 0xFFFE) {
is_new_lib = 1;
} else {
is_new_lib = 0;
is_new_lib = true;
}
else {
is_new_lib = false;
}
}
@ -463,19 +188,18 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
}
#endif
case PCM8: /* assumed, no games known */
vgmstream->coding_type = (fsb.mode & FSOUND_UNSIGNED) ? coding_PCM8_U : coding_PCM8;
case PCM8: /* no games known */
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->interleave_block_size = 0x1;
break;
case PCM16: /* (PCM16) 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 PCM16LE: /* FSB4: Rocket Knight (PC), Another Century's Episode R (PS3), Toy Story 3 (Wii) */
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->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;
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;
return vgmstream;
@ -493,7 +217,7 @@ fail:
}
#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;
STREAMFILE* temp_sf = NULL;
int i, layers = (fsb->channels+1) / 2;
@ -548,61 +272,348 @@ fail:
}
#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).
* Has a 0x10 BE header that holds the filesize (unsure if this is from a proper rip). */
VGMSTREAM* init_vgmstream_fsb4_wav(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE *test_sf = NULL;
off_t subfile_start = 0x10;
size_t subfile_size = get_streamfile_size(sf) - 0x10 - 0x10;
/* 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. */
/* check extensions */
if ( !check_extensions(sf, "fsb,wii") )
goto fail;
/* 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 */
if (read_32bitBE(0x00,sf) != 0x00574156) /* "\0WAV" */
goto fail;
/* 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 */
/* parse FSB subfile */
test_sf = setup_fsb4_wav_streamfile(sf, subfile_start,subfile_size);
if (!test_sf) goto fail;
/* 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
vgmstream = init_vgmstream_fsb(test_sf);
if (!vgmstream) goto fail;
/* init the VGMSTREAM */
close_streamfile(test_sf);
return vgmstream;
/* FSB loop info/flags are kind of wonky, try to make sense of them */
static void fix_loops(fsb_header_t* fsb) {
/* 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:
close_streamfile(test_sf);
close_vgmstream(vgmstream);
return NULL;
/* FMOD tool's default behaviour is creating files with full loops and no flags unless disabled
* manually via flag (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. */
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) {
STREAMFILE *temp_sf = NULL, *new_sf = NULL;
/* covert codec info incomprehensibly defined as bitflags into single codec */
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 */
new_sf = open_wrap_streamfile(sf);
if (!new_sf) goto fail;
temp_sf = new_sf;
if (fsb->codec == MPEG) {
//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"); /* mpeg only? not seen */
new_sf = open_clamp_streamfile(temp_sf, subfile_offset,subfile_size);
if (!new_sf) goto fail;
temp_sf = new_sf;
fsb->mpeg_padding = (fsb->channels > 2 ? 0x10 :
(fsb->flags & FMOD_FSB_SOURCE_MPEG_PADDED4 ? 0x04 :
(fsb->flags & FMOD_FSB_SOURCE_MPEG_PADDED ? 0x02 : 0x000)));
}
new_sf = open_fakename_streamfile(temp_sf, NULL,"fsb");
if (!new_sf) goto fail;
temp_sf = new_sf;
if (fsb->codec == XBOX_IMA) {
/* "interleaved header" IMA, only used with >2ch [Blade Kitten (PC)-6ch]
* 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);
return NULL;
/* officially only for PSX/DSP */
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 */
if ((read_u32be(0x00,sf) & 0xFFFFFF00) == get_id32be("FSB\0"))
goto fail;
return NULL;
/* checks */
/* .fsb: standard
* .fsb.ps3: various Guitar Hero (PS3)
* .fsb.xen: various Guitar Hero (X360/PC) */
if (!check_extensions(sf, "fsb,ps3,xen"))
goto fail;
return NULL;
/* 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);
if (key_size) {
vgmstream = test_fsbkey(sf, key, key_size, MODE_FSBS_ALL);
vgmstream = test_fsbkey(sf, key, key_size, MODE_FSBS);
return vgmstream;
}
}
@ -60,10 +60,10 @@ static VGMSTREAM* test_fsbkey(STREAMFILE* sf, const uint8_t* key, size_t key_siz
if (!key_size)
return NULL;
int test_fsb4 = flags & FLAG_FSB4;
int test_fsb5 = flags & FLAG_FSB5;
int test_std = flags & FLAG_STD;
int test_alt = flags & FLAG_ALT;
bool test_fsb4 = flags & FLAG_FSB4;
bool test_fsb5 = flags & FLAG_FSB5;
bool test_alt = flags & FLAG_ALT;
bool test_std = flags & FLAG_STD;
if (!vc && test_std && test_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>
/*
* 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 {
uint8_t flags;
const char* key;
size_t key_size; /* precalc'd for speed */
} fsbkey_info;
#define FLAG_FSB4 (1 << 0) /* key is valid for FSB4/3 */
#define FLAG_FSB5 (1 << 1) /* key is valid for FSB5 */
#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_FSB4 (1 << 0) /* key is valid for FSB3/4 */
#define FLAG_FSB5 (1 << 1) /* key is valid for FSB5 */
#define FLAG_STD (1 << 2) /* regular XOR mode */
#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_FSB4_ALT (FLAG_FSB4 | FLAG_ALT)
#define MODE_FSB4_ALL (FLAG_FSB4 | FLAG_STD | FLAG_ALT)
#define MODE_FSB5_STD (FLAG_FSB5 | FLAG_STD)
#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)
#define MODE_FSB3 (FLAG_FSB4 | FLAG_ALT)
#define MODE_FSB4 (FLAG_FSB4 | FLAG_STD)
#define MODE_FSB5 (FLAG_FSB5 | FLAG_STD)
#define MODE_FSBS (FLAG_FSB4 | FLAG_FSB5 | FLAG_STD | FLAG_ALT)
/* ugly macro for string + precomputed len (removing string's extra NULL)*/
#define FSBKEY_ADD(key) key, sizeof(key) - 1
static const fsbkey_info fsbkey_list[] = {
{ MODE_FSBS_STD, FSBKEY_ADD("DFm3t4lFTW") }, // Double Fine Productions: Brutal Legend, Massive Chalice, etc (multi)
{ MODE_FSB4_STD, FSBKEY_ADD("nos71RiT") }, // DJ Hero 2 (X360)
{ MODE_FSB5_STD, FSBKEY_ADD("H$#FJa%7gRZZOlxLiN50&g5Q") }, // N++ (PC?)
{ MODE_FSB5_STD, FSBKEY_ADD("sTOoeJXI2LjK8jBMOk8h5IDRNZl3jq3I") }, // Slightly Mad Studios: Project CARS (PC?), World of Speed (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("%lAn2{Pi*Lhw3T}@7*!kV=?qS$@iNlJ") }, // Ghost in the Shell: First Assault (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("1^7%82#&5$~/8sz") }, // RevHeadz Engine Sounds (Mobile)
{ MODE_FSB5_STD, FSBKEY_ADD("FDPrVuT4fAFvdHJYAgyMzRF4EcBAnKg") }, // Dark Souls 3 (PC)
{ MODE_FSB4_STD, FSBKEY_ADD("p&oACY^c4LK5C2v^x5nIO6kg5vNH$tlj") }, // Need for Speed Shift 2 Unleashed (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("996164B5FC0F402983F61F220BB51DC6") }, // Mortal Kombat X/XL (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("logicsounddesignmwsdev") }, // Mirror War: Reincarnation of Holiness (PC)
{ MODE_FSBS_ALL, FSBKEY_ADD("gat@tcqs2010") }, // Xian Xia Chuan (PC) [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("j1$Mk0Libg3#apEr42mo") }, // Critter Crunch (PC), Superbrothers: Sword & Sworcery (PC) [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("@kdj43nKDN^k*kj3ndf02hd95nsl(NJG") }, // Cyphers [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("Xiayuwu69252.Sonicli81223#$*@*0") }, // Xuan Dou Zhi Wang / King of Combat [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("kri_tika_5050_") }, // Ji Feng Zhi Ren / Kritika Online [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("mint78run52") }, // Invisible Inc. (PC?) [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("5atu6w4zaw") }, // Guitar Hero 3 [untested]
{ MODE_FSBS_ALL, FSBKEY_ADD("B2A7BB00") }, // Supreme Commander 2 [untested]
{ MODE_FSB4_STD, FSBKEY_ADD("ghfxhslrghfxhslr") }, // Cookie Run: Ovenbreak
{ MODE_FSB4_ALT, FSBKEY_ADD("truck/impact/carbody") }, // Monster Jam (PS2) [FSB3]
{ 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_FSB5_STD, FSBKEY_ADD("G0KTrWjS9syqF7vVD6RaVXlFD91gMgkC") }, // Sekiro: Shadows Die Twice (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("BasicEncryptionKey") }, // SCP: Unity (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("FXnTffGJ9LS855Gc") }, // Worms Rumble Beta (PC)
{ MODE_FSB4_STD, 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_STD, 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_STD, FSBKEY_ADD("Achilles_0_15_DpG") }, // Achilles: Legends Untold (PC)
{ MODE_FSB5_STD, 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_STD, FSBKEY_ADD("281ad163160cfc16f9a22c6755a64fad") }, // Ash Echoes beta (Android)
{ MODE_FSB5_STD, FSBKEY_ADD("Aurogon666") }, // Afterimage demo (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("IfYouLikeThosesSoundsWhyNotRenumerateTheir2Authors?") }, // Blanc (PC/Switch)
{ MODE_FSB5_STD, FSBKEY_ADD("L36nshM520") }, // Nishuihan Mobile (Android)
{ MODE_FSB5_STD, FSBKEY_ADD("Forza2!") }, // Forza Motorsport (PC)
{ MODE_FSB5_STD, 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_FSB4_STD, FSBKEY_ADD("4DxgpNV3pQLPD6GT7g9Gf6eWU7SXutGQ") }, // Test Drive: Ferrari Racing Legends (PC)
{ MODE_FSB4_STD, FSBKEY_ADD("AjaxIsTheGoodestBoy") }, // Hello Kitty: Island Adventure (iOS)
{ MODE_FSB5_STD, FSBKEY_ADD("resoforce") }, // Rivals of Aether 2 (PC)
{ MODE_FSB4, FSBKEY_ADD("DFm3t4lFTW") }, // Double Fine Productions: Brutal Legend, Massive Chalice, etc (multi)
{ MODE_FSB5, FSBKEY_ADD("DFm3t4lFTW") }, // Double Fine Productions
{ MODE_FSB4, FSBKEY_ADD("nos71RiT") }, // DJ Hero 2 (X360)
{ MODE_FSB5, FSBKEY_ADD("H$#FJa%7gRZZOlxLiN50&g5Q") }, // N++ (PC?)
{ MODE_FSB5, FSBKEY_ADD("sTOoeJXI2LjK8jBMOk8h5IDRNZl3jq3I") }, // Slightly Mad Studios: Project CARS (PC?), World of Speed (PC)
{ MODE_FSB5, FSBKEY_ADD("%lAn2{Pi*Lhw3T}@7*!kV=?qS$@iNlJ") }, // Ghost in the Shell: First Assault (PC)
{ MODE_FSB5, FSBKEY_ADD("1^7%82#&5$~/8sz") }, // RevHeadz Engine Sounds (Mobile)
{ MODE_FSB5, FSBKEY_ADD("FDPrVuT4fAFvdHJYAgyMzRF4EcBAnKg") }, // Dark Souls 3 (PC)
{ MODE_FSB4, FSBKEY_ADD("p&oACY^c4LK5C2v^x5nIO6kg5vNH$tlj") }, // Need for Speed Shift 2 Unleashed (PC)
{ MODE_FSB5, FSBKEY_ADD("996164B5FC0F402983F61F220BB51DC6") }, // Mortal Kombat X/XL (PC)
{ MODE_FSB5, FSBKEY_ADD("logicsounddesignmwsdev") }, // Mirror War: Reincarnation of Holiness (PC)
{ MODE_FSBS, FSBKEY_ADD("gat@tcqs2010") }, // Xian Xia Chuan (PC) [untested]
{ MODE_FSBS, FSBKEY_ADD("j1$Mk0Libg3#apEr42mo") }, // Critter Crunch (PC), Superbrothers: Sword & Sworcery (PC) [untested]
{ MODE_FSBS, FSBKEY_ADD("@kdj43nKDN^k*kj3ndf02hd95nsl(NJG") }, // Cyphers [untested]
{ MODE_FSBS, FSBKEY_ADD("Xiayuwu69252.Sonicli81223#$*@*0") }, // Xuan Dou Zhi Wang / King of Combat [untested]
{ MODE_FSBS, FSBKEY_ADD("kri_tika_5050_") }, // Ji Feng Zhi Ren / Kritika Online [untested]
{ MODE_FSBS, FSBKEY_ADD("mint78run52") }, // Invisible Inc. (PC?) [untested]
{ MODE_FSBS, FSBKEY_ADD("5atu6w4zaw") }, // Guitar Hero 3 [untested]
{ MODE_FSB4, FSBKEY_ADD("B2A7BB00") }, // Supreme Commander 2 (PC)
{ MODE_FSB4, FSBKEY_ADD("ghfxhslrghfxhslr") }, // Cookie Run: Ovenbreak
{ MODE_FSB3, FSBKEY_ADD("truck/impact/carbody") }, // Monster Jam (PS2) [FSB3]
{ MODE_FSB5, FSBKEY_ADD("G0KTrWjS9syqF7vVD6RaVXlFD91gMgkC") }, // Sekiro: Shadows Die Twice (PC)
{ MODE_FSB5, FSBKEY_ADD("BasicEncryptionKey") }, // SCP: Unity (PC)
{ MODE_FSB5, FSBKEY_ADD("FXnTffGJ9LS855Gc") }, // Worms Rumble Beta (PC)
{ MODE_FSB4, FSBKEY_ADD("qjvkeoqkrdhkdckd") }, // Bubble Fighter (PC)
{ MODE_FSB5, FSBKEY_ADD("p@4_ih*srN:UJk&8") }, // Fall Guys (PC) update ~2021-11
{ MODE_FSB5, FSBKEY_ADD(",&.XZ8]fLu%caPF+") }, // Fall Guys (PC) update ~2022-07
{ MODE_FSB5, FSBKEY_ADD("^*4[hE>K]x90Vj") }, // Fall Guys (PC) update ~2023-05
{ MODE_FSB5, FSBKEY_ADD("Achilles_0_15_DpG") }, // Achilles: Legends Untold (PC)
{ MODE_FSB5, FSBKEY_ADD("4FB8CC894515617939F4E1B7D50972D27213B8E6") }, // Cult of the Lamb Demo (PC)
{ MODE_FSB5, FSBKEY_ADD("X3EK%Bbga-%Y9HZZ%gkc*C512*$$DhRxWTGgjUG@=rUD") }, // Signalis (PC)
{ MODE_FSB5, FSBKEY_ADD("281ad163160cfc16f9a22c6755a64fad") }, // Ash Echoes beta (Android)
{ MODE_FSB5, FSBKEY_ADD("Aurogon666") }, // Afterimage demo (PC)
{ MODE_FSB5, FSBKEY_ADD("IfYouLikeThosesSoundsWhyNotRenumerateTheir2Authors?") }, // Blanc (PC/Switch)
{ MODE_FSB5, FSBKEY_ADD("L36nshM520") }, // Nishuihan Mobile (Android)
{ MODE_FSB5, FSBKEY_ADD("Forza2!") }, // Forza Motorsport (PC)
{ MODE_FSB5, FSBKEY_ADD("cbfjZTlUPaZI") }, // JDM: Japanese Drift Master (PC)
{ MODE_FSB3, FSBKEY_ADD("tkdnsem000") }, // Ys Online: The Call of Solum (PC) [FSB3] (alt key: 2ED62676CEA6B60C0C0C)
{ MODE_FSB4, FSBKEY_ADD("4DxgpNV3pQLPD6GT7g9Gf6eWU7SXutGQ") }, // Test Drive: Ferrari Racing Legends (PC)
{ MODE_FSB4, FSBKEY_ADD("AjaxIsTheGoodestBoy") }, // Hello Kitty: Island Adventure (iOS)
{ 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,
so external .fsbkey may be better (use guessfsb 3.1 with --write-key-file or ) */
/* some games use a key per file, generated from the filename
* (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: 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]);

View File

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

View File

@ -208,10 +208,9 @@ VGMSTREAM * init_vgmstream_aus(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_rws(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_fsb(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_fsb4_wav(STREAMFILE * streamFile);
VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf);
VGMSTREAM * init_vgmstream_fsb5(STREAMFILE * streamFile);
VGMSTREAM* init_vgmstream_fsb5(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"); \
} while (0)
#define VGM_STEP() do { printf("%s: %s:%i\n", __FILE__, __FUNCTION__, __LINE__); } while (0)
#else /* VGM_DEBUG_OUTPUT */
#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_STEP() /* nothing */
#endif /*VGM_DEBUG_OUTPUT*/
#endif

View File

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