diff --git a/README.md b/README.md index 026a389a..56cf9287 100644 --- a/README.md +++ b/README.md @@ -74,12 +74,12 @@ for /L %A in (5,1,10) do test.exe -s %A -o file_%A.wav file.fsb ``` Output filename in `-o` may use multiple wildcards: -- `:s`: sets current subsong (or 0 if format doesn't have subsongs) -- `:0Ns`: same, but left pads subsong with up to `N` zeroes -- `:n`: internal stream name, or input filename if format doesn't have name -- `:f`: input filename +- `?s`: sets current subsong (or 0 if format doesn't have subsongs) +- `?0Ns`: same, but left pads subsong with up to `N` zeroes +- `?n`: internal stream name, or input filename if format doesn't have name +- `?f`: input filename -For example `test.exe -s 2 -o :04s_:n.wav file.fsb` could generate `0002_song1.wav` +For example `test.exe -s 2 -o ?04s_?n.wav file.fsb` could generate `0002_song1.wav` ### in_vgmstream diff --git a/audacious/plugin.cc b/audacious/plugin.cc index fa8c238f..dbc8c827 100644 --- a/audacious/plugin.cc +++ b/audacious/plugin.cc @@ -34,8 +34,7 @@ extern "C" { /* global state */ /*EXPORT*/ VgmstreamPlugin aud_plugin_instance; -audacious_settings settings; -VGMSTREAM *vgmstream = NULL; //todo make local? +audacious_settings_t settings; /* Audacious will first send the file to a plugin based on this static extension list. If none * accepts it'll try again all plugins, ordered by priority, until one accepts the file. Problem is, @@ -50,10 +49,11 @@ const char *const VgmstreamPlugin::exts[] = { const char *const VgmstreamPlugin::defaults[] = { "loop_forever", "FALSE", - "loop_count", "2", //maybe double? + "ignore_loop", "FALSE", + "loop_count", "2.0", "fade_length", "10.0", "fade_delay", "0.0", - "downmix_channels", "8", + "downmix_channels", "2", "exts_unknown_on", "FALSE", "exts_common_on", "FALSE", NULL @@ -72,13 +72,15 @@ const char VgmstreamPlugin::about[] = "https://github.com/kode54/vgmstream/\n" "https://sourceforge.net/projects/vgmstream/ (original)"; +/* widget config: {min, max, step} */ const PreferencesWidget VgmstreamPlugin::widgets[] = { WidgetLabel(N_("vgmstream config")), WidgetCheck(N_("Loop forever:"), WidgetBool(settings.loop_forever)), - WidgetSpin(N_("Loop count:"), WidgetInt(settings.loop_count), {1, 20, 1}), - WidgetSpin(N_("Fade length:"), WidgetFloat(settings.fade_length), {0, 60, 0.1}), + WidgetCheck(N_("Ignore loop:"), WidgetBool(settings.ignore_loop)), + WidgetSpin(N_("Loop count:"), WidgetFloat(settings.loop_count), {1, 100, 1.0}), + WidgetSpin(N_("Fade length:"), WidgetFloat(settings.fade_time), {0, 60, 0.1}), WidgetSpin(N_("Fade delay:"), WidgetFloat(settings.fade_delay), {0, 60, 0.1}), - WidgetSpin(N_("Downmix:"), WidgetInt(settings.downmix_channels), {1, 20, 1}), + WidgetSpin(N_("Downmix:"), WidgetInt(settings.downmix_channels), {0, 8, 1}), WidgetCheck(N_("Enable unknown exts"), WidgetBool(settings.exts_unknown_on)), // Audacious 3.6 will only match one plugin so this option has no actual use // (ex. a fake .flac only gets to the FLAC plugin and never to vgmstream, even on error) @@ -89,8 +91,9 @@ void vgmstream_settings_load() { AUDINFO("load settings\n"); aud_config_set_defaults(CFG_ID, VgmstreamPlugin::defaults); settings.loop_forever = aud_get_bool(CFG_ID, "loop_forever"); - settings.loop_count = aud_get_int(CFG_ID, "loop_count"); - settings.fade_length = aud_get_double(CFG_ID, "fade_length"); + settings.ignore_loop = aud_get_bool(CFG_ID, "ignore_loop"); + settings.loop_count = aud_get_double(CFG_ID, "loop_count"); + settings.fade_time = aud_get_double(CFG_ID, "fade_length"); settings.fade_delay = aud_get_double(CFG_ID, "fade_delay"); settings.downmix_channels = aud_get_int(CFG_ID, "downmix_channels"); settings.exts_unknown_on = aud_get_bool(CFG_ID, "exts_unknown_on"); @@ -100,8 +103,9 @@ void vgmstream_settings_load() { void vgmstream_settings_save() { AUDINFO("save settings\n"); aud_set_bool(CFG_ID, "loop_forever", settings.loop_forever); - aud_set_int(CFG_ID, "loop_count", settings.loop_count); - aud_set_double(CFG_ID, "fade_length", settings.fade_length); + aud_set_bool(CFG_ID, "ignore_loop", settings.ignore_loop); + aud_set_double(CFG_ID, "loop_count", settings.loop_count); + aud_set_double(CFG_ID, "fade_length", settings.fade_time); aud_set_double(CFG_ID, "fade_delay", settings.fade_delay); aud_set_int(CFG_ID, "downmix_channels", settings.downmix_channels); aud_set_bool(CFG_ID, "exts_unknown_on", settings.exts_unknown_on); @@ -113,7 +117,7 @@ const PluginPreferences VgmstreamPlugin::prefs = { }; // validate extension (thread safe) -bool VgmstreamPlugin::is_our_file(const char *filename, VFSFile &file) { +bool VgmstreamPlugin::is_our_file(const char * filename, VFSFile & file) { AUDDBG("test file=%s\n", filename); vgmstream_ctx_valid_cfg cfg = {0}; @@ -140,7 +144,7 @@ void VgmstreamPlugin::cleanup() { } #if 0 -static int get_filename_subtune(const char * filename) { +static int get_filename_subtune(const char* filename) { int subtune; int pos = strstr("?")) @@ -152,8 +156,21 @@ static int get_filename_subtune(const char * filename) { } #endif +static void apply_config(VGMSTREAM* vgmstream, audacious_settings_t* settings) { + vgmstream_cfg_t vcfg = {0}; + + vcfg.allow_play_forever = 1; + vcfg.play_forever = settings->loop_forever; + vcfg.loop_count = settings->loop_count; + vcfg.fade_time = settings->fade_time; + vcfg.fade_delay = settings->fade_delay; + vcfg.ignore_loop = settings->ignore_loop; + + vgmstream_apply_config(vgmstream, &vcfg); +} + // internal helper, called every time user adds a new file to playlist -static bool read_info(const char * filename, Tuple & tuple) { +static bool read_info(const char* filename, Tuple & tuple) { AUDINFO("read file=%s\n", filename); #if 0 @@ -167,12 +184,12 @@ static bool read_info(const char * filename, Tuple & tuple) { //must use basename to open streamfile #endif - STREAMFILE *streamfile = open_vfs(filename); - if (!streamfile) return false; + STREAMFILE* sf = open_vfs(filename); + if (!sf) return false; - VGMSTREAM *infostream = init_vgmstream_from_STREAMFILE(streamfile); + VGMSTREAM* infostream = init_vgmstream_from_STREAMFILE(sf); if (!infostream) { - close_streamfile(streamfile); + close_streamfile(sf); return false; } @@ -189,24 +206,26 @@ static bool read_info(const char * filename, Tuple & tuple) { return true; //must return? } - streamfile->stream_index = (subtune + 1); + sf->stream_index = (subtune + 1); #endif - //todo apply_config + apply_config(infostream, &settings); + int output_channels = infostream->channels; vgmstream_mixing_autodownmix(infostream, settings.downmix_channels); vgmstream_mixing_enable(infostream, 0, NULL, &output_channels); int bitrate = get_vgmstream_average_bitrate(infostream); - int ms = get_vgmstream_play_samples(settings.loop_count, settings.fade_length, settings.fade_delay, infostream); - ms = ms* 1000LL / infostream->sample_rate; + int length_samples = vgmstream_get_samples(infostream); + int length_ms = length_samples * 1000LL / infostream->sample_rate; + //todo: set_format may throw std::bad_alloc if output_channels isn't supported (only 2?) // short form, not sure if better way tuple.set_format("vgmstream codec", output_channels, infostream->sample_rate, bitrate); tuple.set_filename(filename); //used? tuple.set_int(Tuple::Bitrate, bitrate); //in kb/s - tuple.set_int(Tuple::Length, ms); + tuple.set_int(Tuple::Length, length_ms); //todo here we could call describe_vgmstream() and get substring to add tags and stuff tuple.set_str(Tuple::Codec, "vgmstream codec"); @@ -218,14 +237,14 @@ static bool read_info(const char * filename, Tuple & tuple) { //tuple.set_str (Tuple::Artist, ...); //tuple.set_str (Tuple::Album, ...); - close_streamfile(streamfile); + close_streamfile(sf); close_vgmstream(infostream); return true; } // thread safe (for Audacious <= 3.7, unused otherwise) -Tuple VgmstreamPlugin::read_tuple(const char *filename, VFSFile &file) { +Tuple VgmstreamPlugin::read_tuple(const char * filename, VFSFile & file) { Tuple tuple; read_info(filename, tuple); return tuple; @@ -237,61 +256,33 @@ bool VgmstreamPlugin::read_tag(const char * filename, VFSFile & file, Tuple & tu } // internal util to seek during play -static void seek_helper(int seek_value, int ¤t_sample_pos, int input_channels) { +static void do_seek(VGMSTREAM* vgmstream, int seek_ms, int& current_sample_pos) { AUDINFO("seeking\n"); // compute from ms to samples - int seek_needed_samples = (long long)seek_value * vgmstream->sample_rate / 1000L; - short buffer[MIN_BUFFER_SIZE * input_channels]; - int max_buffer_samples = MIN_BUFFER_SIZE; + int seek_sample = (long long)seek_ms * vgmstream->sample_rate / 1000L; - int samples_to_do = 0; - if (seek_needed_samples < current_sample_pos) { - // go back in time, reopen file - AUDINFO("resetting file to seek backwards\n"); - reset_vgmstream(vgmstream); - current_sample_pos = 0; - samples_to_do = seek_needed_samples; - } else if (current_sample_pos < seek_needed_samples) { - // go forward in time - samples_to_do = seek_needed_samples - current_sample_pos; - } + seek_vgmstream(vgmstream, seek_sample); - // do the actual seeking - if (samples_to_do >= 0) { - AUDINFO("rendering forward\n"); - - // render till seeked sample - while (samples_to_do > 0) { - int seek_samples = std::min(max_buffer_samples, samples_to_do); - current_sample_pos += seek_samples; - samples_to_do -= seek_samples; - render_vgmstream(buffer, seek_samples, vgmstream); - } - } + current_sample_pos = seek_sample; } // called on play (play thread) -bool VgmstreamPlugin::play(const char *filename, VFSFile &file) { +bool VgmstreamPlugin::play(const char * filename, VFSFile & file) { AUDINFO("play file=%s\n", filename); - // just in case - if (vgmstream) - close_vgmstream(vgmstream); - - STREAMFILE *streamfile = open_vfs(filename); - if (!streamfile) { + STREAMFILE* sf = open_vfs(filename); + if (!sf) { AUDERR("failed opening file %s\n", filename); return false; } - vgmstream = init_vgmstream_from_STREAMFILE(streamfile); - close_streamfile(streamfile); + VGMSTREAM* vgmstream = init_vgmstream_from_STREAMFILE(sf); + close_streamfile(sf); if (!vgmstream) { AUDINFO("filename %s is not a valid format\n", filename); close_vgmstream(vgmstream); - vgmstream = NULL; return false; } @@ -300,6 +291,9 @@ bool VgmstreamPlugin::play(const char *filename, VFSFile &file) { //todo apply config + + apply_config(vgmstream, &settings); + int input_channels = vgmstream->channels; int output_channels = vgmstream->channels; /* enable after all config but before outbuf */ @@ -312,53 +306,38 @@ bool VgmstreamPlugin::play(const char *filename, VFSFile &file) { // play short buffer[MIN_BUFFER_SIZE * input_channels]; int max_buffer_samples = MIN_BUFFER_SIZE; - int stream_samples_amount = get_vgmstream_play_samples( - settings.loop_count, settings.fade_length, - settings.fade_delay, vgmstream); - int fade_samples = settings.fade_length * vgmstream->sample_rate; - int current_sample_pos = 0; + + int play_forever = vgmstream_get_play_forever(vgmstream); + int length_samples = vgmstream_get_samples(vgmstream); + int decode_pos_samples = 0; while (!check_stop()) { - int toget = max_buffer_samples; + int to_do = max_buffer_samples; // handle seek request int seek_value = check_seek(); - if (seek_value >= 0) - seek_helper(seek_value, current_sample_pos, input_channels); + if (seek_value >= 0) { + do_seek(vgmstream, seek_value, decode_pos_samples); + continue; + } // check stream finished - if (!settings.loop_forever || !vgmstream->loop_flag) { - if (current_sample_pos >= stream_samples_amount) + if (!play_forever) { + if (decode_pos_samples >= length_samples) break; - if (current_sample_pos + toget > stream_samples_amount) - toget = stream_samples_amount - current_sample_pos; + + if (decode_pos_samples + to_do > length_samples) + to_do = length_samples - decode_pos_samples; } - render_vgmstream(buffer, toget, vgmstream); + render_vgmstream(buffer, to_do, vgmstream); - if (vgmstream->loop_flag && fade_samples > 0 && - !settings.loop_forever) { - int samples_into_fade = - current_sample_pos - (stream_samples_amount - fade_samples); - if (samples_into_fade + toget > 0) { - for (int j = 0; j < toget; j++, samples_into_fade++) { - if (samples_into_fade > 0) { - double fadedness = - (double)(fade_samples - samples_into_fade) / fade_samples; - for (int k = 0; k < output_channels; k++) - buffer[j * output_channels + k] *= fadedness; - } - } - } - } - - write_audio(buffer, toget * sizeof(short) * output_channels); - current_sample_pos += toget; + write_audio(buffer, to_do * sizeof(short) * output_channels); + decode_pos_samples += to_do; } AUDINFO("play finished\n"); close_vgmstream(vgmstream); - vgmstream = NULL; return true; } diff --git a/audacious/plugin.h b/audacious/plugin.h index 4cf6264d..5cb8ce80 100644 --- a/audacious/plugin.h +++ b/audacious/plugin.h @@ -35,25 +35,26 @@ public: bool init(); void cleanup(); - bool is_our_file(const char *filename, VFSFile &file); - Tuple read_tuple(const char *filename, VFSFile &file); + bool is_our_file(const char * filename, VFSFile & file); + Tuple read_tuple(const char * filename, VFSFile & file); bool read_tag(const char * filename, VFSFile & file, Tuple & tuple, Index * image); - bool play(const char *filename, VFSFile &file); + bool play(const char * filename, VFSFile & file); }; typedef struct { bool loop_forever; - int loop_count; - double fade_length; + bool ignore_loop; + double loop_count; + double fade_time; double fade_delay; int downmix_channels; bool exts_unknown_on; bool exts_common_on; -} audacious_settings; +} audacious_settings_t; -extern audacious_settings settings; +extern audacious_settings_t settings; void vgmstream_settings_load(); void vgmstream_settings_save(); diff --git a/cli/vgmstream_cli.c b/cli/vgmstream_cli.c index 213d953c..2e866756 100644 --- a/cli/vgmstream_cli.c +++ b/cli/vgmstream_cli.c @@ -37,7 +37,7 @@ static void usage(const char* name, int is_full) { "Usage: %s [-o ] [options] \n" "Options:\n" " -o : name of output .wav file, default .wav\n" - " wildcards can be :s=subsong, :n=stream name, :f=infile\n" + " wildcards can be ?s=subsong, ?n=stream name, ?f=infile\n" " -l loop count: loop count, default 2.0\n" " -f fade time: fade time in seconds after N loops, default 10.0\n" " -d fade delay: fade delay in seconds, default 0.0\n" @@ -55,18 +55,20 @@ static void usage(const char* name, int is_full) { " -x: decode and print adxencd command line to encode as ADX\n" " -g: decode and print oggenc command line to encode as OGG\n" " -b: decode and print batch variable commands\n" - " -h: print extra commands\n" + " -h: print extra commands (for testing)\n" , name); - if (is_full) { - fprintf(stderr, - " -v: validate extensions (for extension testing)\n" - " -r: output a second file after resetting (for reset testing)\n" - " -k N: seeks to N samples before decoding (for seek testing)\n" - " -K N: seeks to N samples before decoding again (for seek testing)\n" - " -t file: print tags found in file (for tag testing)\n" - " -O: decode but don't write to file (for performance testing)\n" - ); - } + if (!is_full) + return; + fprintf(stderr, + " -v: validate extensions (for extension testing)\n" + " -r: output a second file after resetting (for reset testing)\n" + " -k N: seeks to N samples before decoding (for seek testing)\n" + " -K N: seeks again to N samples before decoding (for seek testing)\n" + " -t file: print tags found in file (for tag testing)\n" + " -D : downmix to (for plugin downmix testing)\n" + " -O: decode but don't write to file (for performance testing)\n" + ); + } @@ -98,6 +100,7 @@ typedef struct { int seek_samples1; int seek_samples2; int decode_only; + int downmix_channels; /* not quite config but eh */ int lwav_loop_start; @@ -119,7 +122,7 @@ static int parse_config(cli_config* cfg, int argc, char** argv) { opterr = 0; /* read config */ - while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:k:K:hOv")) != -1) { + while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:k:K:hOvD:")) != -1) { switch (opt) { case 'o': cfg->outfilename = optarg; @@ -194,6 +197,9 @@ static int parse_config(cli_config* cfg, int argc, char** argv) { case 'v': cfg->validate_extensions = 1; break; + case 'D': + cfg->downmix_channels = atoi(optarg); + break; case 'h': usage(argv[0], 1); goto fail; @@ -306,8 +312,8 @@ static void apply_config(VGMSTREAM* vgmstream, cli_config* cfg) { } vcfg.play_forever = cfg->play_forever; - vcfg.fade_period = cfg->fade_time; - vcfg.loop_times = cfg->loop_count; + vcfg.fade_time = cfg->fade_time; + vcfg.loop_count = cfg->loop_count; vcfg.fade_delay = cfg->fade_delay; vcfg.ignore_loop = cfg->ignore_loop; @@ -356,8 +362,8 @@ static void clean_filename(char* dst, int clean_paths) { } } -/* replaces a filename with ":n" (stream name), ":f" (infilename) or ":s" (subsong) wildcards - * (":" was chosen since it's not a valid Windows filename char and hopefully nobody uses it on Linux) */ +/* replaces a filename with "?n" (stream name), "?f" (infilename) or "?s" (subsong) wildcards + * ("?" was chosen since it's not a valid Windows filename char and hopefully nobody uses it on Linux) */ void replace_filename(char* dst, size_t dstsize, const char* outfilename, const char* infilename, VGMSTREAM* vgmstream) { int subsong; char stream_name[PATH_LIMIT]; @@ -390,29 +396,33 @@ void replace_filename(char* dst, size_t dstsize, const char* outfilename, const /* do controlled replaces of each wildcard (in theory could appear N times) */ do { - char* pos = strchr(buf, ':'); + char* pos = strchr(buf, '?'); if (!pos) break; /* use buf as format and copy formatted result to tmp (assuming sprintf's format must not overlap with dst) */ - pos[0] = '%'; if (pos[1] == 'n') { + pos[0] = '%'; pos[1] = 's'; /* use %s */ snprintf(tmp, sizeof(tmp), buf, stream_name); } else if (pos[1] == 'f') { + pos[0] = '%'; pos[1] = 's'; /* use %s */ snprintf(tmp, sizeof(tmp), buf, infilename); } else if (pos[1] == 's') { + pos[0] = '%'; pos[1] = 'i'; /* use %i */ snprintf(tmp, sizeof(tmp), buf, subsong); } else if ((pos[1] == '0' && pos[2] >= '1' && pos[2] <= '9' && pos[3] == 's')) { + pos[0] = '%'; pos[3] = 'i'; /* use %0Ni */ snprintf(tmp, sizeof(tmp), buf, subsong); } else { + /* not recognized */ continue; } @@ -503,6 +513,8 @@ int main(int argc, char** argv) { input_channels = vgmstream->channels; /* enable after config but before outbuf */ + if (cfg.downmix_channels) + vgmstream_mixing_autodownmix(vgmstream, cfg.downmix_channels); vgmstream_mixing_enable(vgmstream, SAMPLE_BUFFER_SIZE, &input_channels, &channels); /* get final play config */ @@ -529,7 +541,7 @@ int main(int argc, char** argv) { /* maybe should avoid overwriting with this auto-name, for the unlikely * case of file header-body pairs (file.ext+file.ext.wav) */ } - else if (strchr(cfg.outfilename, ':') != NULL) { + else if (strchr(cfg.outfilename, '?') != NULL) { /* special substitution */ replace_filename(outfilename_temp, sizeof(outfilename_temp), cfg.outfilename, cfg.infilename, vgmstream); cfg.outfilename = outfilename_temp; diff --git a/configure.ac b/configure.ac index 4d460b9c..74265a37 100644 --- a/configure.ac +++ b/configure.ac @@ -35,6 +35,12 @@ PKG_CHECK_MODULES(MPG123, [libmpg123], have_libmpg123=yes, ) AM_CONDITIONAL(HAVE_LIBMPG123, test "$have_libmpg123" = yes) +#have_ffmpeg=no +#PKG_CHECK_MODULES(FFMPEG, [libavformat libavcodec libavutil libswresample], have_ffmpeg=yes, +# [AC_MSG_WARN([Cannot find ffmpeg - will not enable FFmpeg formats])] +#) +#AM_CONDITIONAL(HAVE_FFMPEG, test "$have_ffmpeg" = yes) + have_audacious=no PKG_CHECK_MODULES(AUDACIOUS, [audacious >= 3.5.0], have_audacious=yes, [AC_MSG_WARN([Cannot find audacious >= 3.5.0 correctly installed - will not build Audacious plugin])] @@ -55,6 +61,7 @@ then CFLAGS="$CFLAGS -Wall -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-but-set-variable" fi +#plugindir=`pkg-config audacious --define-variable=prefix="$prefix" --variable=plugin_dir` plugindir=`pkg-config audacious --variable=plugin_dir` AC_SUBST(plugindir) diff --git a/doc/BUILD.md b/doc/BUILD.md index 9c0a3e85..07159457 100644 --- a/doc/BUILD.md +++ b/doc/BUILD.md @@ -91,7 +91,7 @@ msbuild fb2k/foo_input_vgmstream.vcxproj ^ ``` ### Audacious plugin -Requires the dev version of Audacious (and dependencies), automake/autoconf, and gcc/make (C++11). It must be compiled and installed into Audacious, where it should appear in the plugin list as "vgmstream". +Requires the dev version of Audacious (and dependencies), automake/autoconf or cmake, and gcc/make (C++11). It must be compiled and installed into Audacious, where it should appear in the plugin list as "vgmstream". The plugin needs Audacious 3.5 or higher. New Audacious releases can break plugin compatibility so it may not work with the latest version unless adapted first. @@ -99,7 +99,9 @@ libvorbis and libmpg123 will be used if found, while FFmpeg and other external l Windows builds aren't supported at the moment (should be possible but there are complex dependency chains). -Take note of other plugins stealing extensions (see README). To change Audacious's default priority for vgmstream you can make with CFLAG `AUDACIOUS_VGMSTREAM_PRIORITY n` (where `N` is number with 10=lowest) +If you get errors during the build phase we probably forgot some `#ifdef` needed for Audacious, notify and should be quickly fixed. + +Take note of other plugins stealing extensions (see README). To change Audacious's default priority for vgmstream you can make with CFLAG `AUDACIOUS_VGMSTREAM_PRIORITY n` (where `N` is a number where 10=lowest) Terminal example, assuming a Ubuntu-based Linux distribution: @@ -113,22 +115,22 @@ sudo apt-get install autoconf automake libtool sudo apt-get install git # vgmstream dependencies sudo apt-get install libmpg123-dev libvorbis-dev +#sudo apt-get install libavformat-dev libavcodec-dev libavutil-dev libswresample-dev # Audacious player and dependencies sudo apt-get install audacious sudo apt-get install audacious-dev libglib2.0-dev libgtk2.0-dev libpango1.0-dev - -# if you want vgmstream123 do this too +# vgmstream123 dependencies (optional) sudo apt-get install libao-dev # check Audacious version >= 3.5 pkg-config --modversion audacious ``` ``` -# vgmstream build - -git clone https://github.com/kode54/vgmstream +# base vgmstream build +git clone https://github.com/losnoco/vgmstream cd vgmstream +# main vgmstream build (if you get errors here please report) ./bootstrap ./configure make -f Makefile.autotools @@ -158,8 +160,19 @@ find . -name ".deps" -type d -exec rm -r "{}" \; ## WARNING, removes *all* untracked files not in .gitignore git clean -fd ``` -To update vgmstream it's probably easiest to remove the `vgmstream` folder and start again from "vgmstream build" step, since updates often require a full rebuild anyway. +To update vgmstream it's probably easiest to remove the `vgmstream` folder and start again from *base vgmstream build* step, since updates often require a full rebuild anyway. +Instead of autotools you can try building with CMake. Some older distros may not work though (CMake version needs to recognize FILTER command). You may need to install resulting artifacts manually. +``` +sudo apt-get update +sudo apt-get install -y libmpg123-dev libvorbis-dev +sudo apt-get install -y libavformat-dev libavcodec-dev libavutil-dev libswresample-dev +sudo apt-get install -y libao-dev audacious-dev +sudo apt-get install -y cmake + +cmake . +make +``` ### vgmstream123 player Should be buildable with Autotools by following the same steps as listen in the Audacious section (requires libao-dev). diff --git a/fb2k/foo_vgmstream.cpp b/fb2k/foo_vgmstream.cpp index b74c1625..ba2bccc8 100644 --- a/fb2k/foo_vgmstream.cpp +++ b/fb2k/foo_vgmstream.cpp @@ -486,8 +486,8 @@ void input_vgmstream::apply_config(VGMSTREAM* vgmstream) { vcfg.allow_play_forever = 1; vcfg.play_forever = loop_forever; - vcfg.loop_times = loop_count; - vcfg.fade_period = fade_seconds; + vcfg.loop_count = loop_count; + vcfg.fade_time = fade_seconds; vcfg.fade_delay = fade_delay_seconds; vcfg.ignore_loop = ignore_loop; diff --git a/src/Makefile.autotools.am b/src/Makefile.autotools.am index 7202e9c1..8bf49834 100644 --- a/src/Makefile.autotools.am +++ b/src/Makefile.autotools.am @@ -23,7 +23,13 @@ AM_CFLAGS += -DVGM_USE_VORBIS libvgmstream_la_LIBADD += $(VORBISFILE_LIBS) $(VORBIS_LIBS) endif endif + if HAVE_LIBMPG123 AM_CFLAGS += -DVGM_USE_MPEG libvgmstream_la_LIBADD += $(MPG123_LIBS) endif + +if HAVE_FFMPEG +AM_CFLAGS += -DVGM_USE_FFMPEG +libvgmstream_la_LIBADD += $(FFMPEG_LIBS) +endif diff --git a/src/coding/Makefile.autotools.am b/src/coding/Makefile.autotools.am index 14669050..8ea74394 100644 --- a/src/coding/Makefile.autotools.am +++ b/src/coding/Makefile.autotools.am @@ -17,6 +17,11 @@ if HAVE_VORBISFILE AM_CFLAGS += -DVGM_USE_VORBIS endif endif + if HAVE_LIBMPG123 AM_CFLAGS += -DVGM_USE_MPEG endif + +if HAVE_FFMPEG +AM_CFLAGS += -DVGM_USE_FFMPEG +endif diff --git a/src/coding/coding.h b/src/coding/coding.h index c5c70ca6..7a241030 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -605,19 +605,6 @@ size_t aac_get_samples(STREAMFILE* sf, off_t start_offset, size_t bytes); size_t mpeg_get_samples(STREAMFILE* sf, off_t start_offset, size_t bytes); -/* An internal struct to pass around and simulate a bitstream. */ -typedef enum { BITSTREAM_MSF, BITSTREAM_VORBIS } vgm_bitstream_t; -typedef struct { - uint8_t* buf; /* buffer to read/write*/ - size_t bufsize; /* max size of the buffer */ - off_t b_off; /* current offset in bits inside the buffer */ - vgm_bitstream_t mode; /* read/write modes */ -} vgm_bitstream; - -int r_bits(vgm_bitstream* ib, int num_bits, uint32_t* value); -int w_bits(vgm_bitstream* ob, int num_bits, uint32_t value); - - /* helper to pass a wrapped, clamped, fake extension-ed, SF to another meta */ STREAMFILE* setup_subfile_streamfile(STREAMFILE* sf, off_t subfile_offset, size_t subfile_size, const char* extension); diff --git a/src/coding/coding_utils.c b/src/coding/coding_utils.c index 4a28b718..faddd023 100644 --- a/src/coding/coding_utils.c +++ b/src/coding/coding_utils.c @@ -1012,152 +1012,6 @@ size_t aac_get_samples(STREAMFILE *streamFile, off_t start_offset, size_t bytes) return frames * samples_per_frame; } - -/* ******************************************** */ -/* BITSTREAM */ -/* ******************************************** */ - - -/* Read bits (max 32) from buf and update the bit offset. Vorbis packs values in LSB order and byte by byte. - * (ex. from 2 bytes 00100111 00000001 we can could read 4b=0111 and 6b=010010, 6b=remainder (second value is split into the 2nd byte) */ -static int r_bits_vorbis(vgm_bitstream * ib, int num_bits, uint32_t * value) { - off_t off, pos; - int i, bit_buf, bit_val; - if (num_bits == 0) return 1; - if (num_bits > 32 || num_bits < 0 || ib->b_off + num_bits > ib->bufsize*8) goto fail; - - *value = 0; /* set all bits to 0 */ - off = ib->b_off / 8; /* byte offset */ - pos = ib->b_off % 8; /* bit sub-offset */ - for (i = 0; i < num_bits; i++) { - bit_buf = (1U << pos) & 0xFF; /* bit check for buf */ - bit_val = (1U << i); /* bit to set in value */ - - if (ib->buf[off] & bit_buf) /* is bit in buf set? */ - *value |= bit_val; /* set bit */ - - pos++; /* new byte starts */ - if (pos%8 == 0) { - pos = 0; - off++; - } - } - - ib->b_off += num_bits; - return 1; -fail: - return 0; -} - -/* Write bits (max 32) to buf and update the bit offset. Vorbis packs values in LSB order and byte by byte. - * (ex. writing 1101011010 from b_off 2 we get 01101011 00001101 (value split, and 11 in the first byte skipped)*/ -static int w_bits_vorbis(vgm_bitstream * ob, int num_bits, uint32_t value) { - off_t off, pos; - int i, bit_val, bit_buf; - if (num_bits == 0) return 1; - if (num_bits > 32 || num_bits < 0 || ob->b_off + num_bits > ob->bufsize*8) goto fail; - - - off = ob->b_off / 8; /* byte offset */ - pos = ob->b_off % 8; /* bit sub-offset */ - for (i = 0; i < num_bits; i++) { - bit_val = (1U << i); /* bit check for value */ - bit_buf = (1U << pos) & 0xFF; /* bit to set in buf */ - - if (value & bit_val) /* is bit in val set? */ - ob->buf[off] |= bit_buf; /* set bit */ - else - ob->buf[off] &= ~bit_buf; /* unset bit */ - - pos++; /* new byte starts */ - if (pos%8 == 0) { - pos = 0; - off++; - } - } - - ob->b_off += num_bits; - return 1; -fail: - return 0; -} - - -/* Read bits (max 32) from buf and update the bit offset. Order is BE (MSF). */ -static int r_bits_msf(vgm_bitstream * ib, int num_bits, uint32_t * value) { - off_t off, pos; - int i, bit_buf, bit_val; - if (num_bits == 0) return 1; - if (num_bits > 32 || num_bits < 0 || ib->b_off + num_bits > ib->bufsize*8) goto fail; - - *value = 0; /* set all bits to 0 */ - off = ib->b_off / 8; /* byte offset */ - pos = ib->b_off % 8; /* bit sub-offset */ - for (i = 0; i < num_bits; i++) { - bit_buf = (1U << (8-1-pos)) & 0xFF; /* bit check for buf */ - bit_val = (1U << (num_bits-1-i)); /* bit to set in value */ - - if (ib->buf[off] & bit_buf) /* is bit in buf set? */ - *value |= bit_val; /* set bit */ - - pos++; - if (pos%8 == 0) { /* new byte starts */ - pos = 0; - off++; - } - } - - ib->b_off += num_bits; - return 1; -fail: - return 0; -} - -/* Write bits (max 32) to buf and update the bit offset. Order is BE (MSF). */ -static int w_bits_msf(vgm_bitstream * ob, int num_bits, uint32_t value) { - off_t off, pos; - int i, bit_val, bit_buf; - if (num_bits == 0) return 1; - if (num_bits > 32 || num_bits < 0 || ob->b_off + num_bits > ob->bufsize*8) goto fail; - - - off = ob->b_off / 8; /* byte offset */ - pos = ob->b_off % 8; /* bit sub-offset */ - for (i = 0; i < num_bits; i++) { - bit_val = (1U << (num_bits-1-i)); /* bit check for value */ - bit_buf = (1U << (8-1-pos)) & 0xFF; /* bit to set in buf */ - - if (value & bit_val) /* is bit in val set? */ - ob->buf[off] |= bit_buf; /* set bit */ - else - ob->buf[off] &= ~bit_buf; /* unset bit */ - - pos++; - if (pos%8 == 0) { /* new byte starts */ - pos = 0; - off++; - } - } - - ob->b_off += num_bits; - return 1; -fail: - return 0; -} - -int r_bits(vgm_bitstream * ib, int num_bits, uint32_t * value) { - if (ib->mode == BITSTREAM_VORBIS) - return r_bits_vorbis(ib,num_bits,value); - else - return r_bits_msf(ib,num_bits,value); -} -int w_bits(vgm_bitstream * ob, int num_bits, uint32_t value) { - if (ob->mode == BITSTREAM_VORBIS) - return w_bits_vorbis(ob,num_bits,value); - else - return w_bits_msf(ob,num_bits,value); -} - /* ******************************************** */ /* CUSTOM STREAMFILES */ /* ******************************************** */ diff --git a/src/coding/l5_555_decoder.c b/src/coding/l5_555_decoder.c index 0a12e837..7b8a7aa4 100644 --- a/src/coding/l5_555_decoder.c +++ b/src/coding/l5_555_decoder.c @@ -8,48 +8,59 @@ static const int32_t l5_scales[32] = { 0x00130B82, 0x00182B83, 0x001EAC92, 0x0026EDB2, 0x00316777, 0x003EB2E6, 0x004F9232, 0x0064FBD1 }; -void decode_l5_555(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { - int i=first_sample; - int32_t sample_count; +void decode_l5_555(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { + uint8_t frame[0x12] = {0}; + off_t frame_offset; + int i, frames_in, sample_count = 0; + size_t bytes_per_frame, samples_per_frame; + uint16_t header; + uint8_t coef_index; - int framesin = first_sample/32; - - uint16_t header = (uint16_t)read_16bitLE(framesin*0x12+stream->offset,stream->streamfile); - int32_t pos_scale = l5_scales[(header>>5)&0x1f]; - int32_t neg_scale = l5_scales[header&0x1f]; - - int coef_index = (header >> 10) & 0x1f; int16_t hist1 = stream->adpcm_history1_16; int16_t hist2 = stream->adpcm_history2_16; int16_t hist3 = stream->adpcm_history3_16; - int32_t coef1 = stream->adpcm_coef_3by32[coef_index*3]; - int32_t coef2 = stream->adpcm_coef_3by32[coef_index*3+1]; - int32_t coef3 = stream->adpcm_coef_3by32[coef_index*3+2]; - /*printf("offset: %x\nscale: %d\nindex: %d (%lf,%lf)\nhist: %d %d\n", - (unsigned)stream->offset,scale,coef_index,coef1/2048.0,coef2/2048.0,hist1,hist2);*/ + int32_t coef1, coef2, coef3; + int32_t pos_scale, neg_scale; - first_sample = first_sample%32; + /* external interleave (fixed size), mono */ + bytes_per_frame = 0x12; + samples_per_frame = (bytes_per_frame - 0x02) * 2; /* always 32 */ + frames_in = first_sample / samples_per_frame; + first_sample = first_sample % samples_per_frame; - for (i=first_sample,sample_count=0; ioffset+2+i/2,stream->streamfile); - int nibble = (i&1? - get_low_nibble_signed(sample_byte): - get_high_nibble_signed(sample_byte)); - int32_t prediction = - -(hist1 * coef1 + hist2 * coef2 + hist3 * coef3); + /* parse frame header */ + frame_offset = stream->offset + bytes_per_frame * frames_in; + read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */ + header = get_u32le(frame); + coef_index = (header >> 10) & 0x1f; + pos_scale = l5_scales[(header >> 5) & 0x1f]; + neg_scale = l5_scales[(header >> 0) & 0x1f]; - if (nibble >= 0) - { - outbuf[sample_count] = clamp16((prediction + nibble * pos_scale) >> 12); - } + coef1 = stream->adpcm_coef_3by32[coef_index * 3 + 0]; + coef2 = stream->adpcm_coef_3by32[coef_index * 3 + 1]; + coef3 = stream->adpcm_coef_3by32[coef_index * 3 + 2]; + + for (i = first_sample; i < first_sample + samples_to_do; i++) { + int32_t prediction, sample = 0; + uint8_t nibbles = frame[0x02 + i/2]; + + sample = (i&1) ? + get_low_nibble_signed(nibbles): + get_high_nibble_signed(nibbles); + prediction = -(hist1 * coef1 + hist2 * coef2 + hist3 * coef3); + + if (sample >= 0) + sample = (prediction + sample * pos_scale) >> 12; else - { - outbuf[sample_count] = clamp16((prediction + nibble * neg_scale) >> 12); - } + sample = (prediction + sample * neg_scale) >> 12; + sample = clamp16(sample); + + outbuf[sample_count] = sample; + sample_count += channelspacing; hist3 = hist2; hist2 = hist1; - hist1 = outbuf[sample_count]; + hist1 = sample; } stream->adpcm_history1_16 = hist1; diff --git a/src/coding/mpeg_bitreader.h b/src/coding/mpeg_bitreader.h new file mode 100644 index 00000000..345f8aba --- /dev/null +++ b/src/coding/mpeg_bitreader.h @@ -0,0 +1,91 @@ +#ifndef _MPEG_BITREADER_H +#define _MPEG_BITREADER_H + +/* Simple bitreader for MPEG/standard bit format. + * Kept in .h since it's slightly faster (compiler can optimize statics better) */ + + +typedef struct { + uint8_t* buf; /* buffer to read/write */ + size_t bufsize; /* max size of the buffer */ + uint32_t b_off; /* current offset in bits inside the buffer */ +} bitstream_t; + +/* convenience util */ +static void init_bitstream(bitstream_t* b, uint8_t* buf, size_t bufsize) { + b->buf = buf; + b->bufsize = bufsize; + b->b_off = 0; +} + +/* Read bits (max 32) from buf and update the bit offset. Order is BE (MSF). */ +static int rb_bits(bitstream_t* ib, uint32_t bits, uint32_t* value) { + uint32_t shift, pos, val; + int i, bit_buf, bit_val; + + if (bits > 32 || ib->b_off + bits > ib->bufsize * 8) + goto fail; + + pos = ib->b_off / 8; /* byte offset */ + shift = ib->b_off % 8; /* bit sub-offset */ + + val = 0; + for (i = 0; i < bits; i++) { + bit_buf = (1U << (8-1-shift)) & 0xFF; /* bit check for buf */ + bit_val = (1U << (bits-1-i)); /* bit to set in value */ + + if (ib->buf[pos] & bit_buf) /* is bit in buf set? */ + val |= bit_val; /* set bit */ + + shift++; + if (shift % 8 == 0) { /* new byte starts */ + shift = 0; + pos++; + } + } + + *value = val; + ib->b_off += bits; + return 1; +fail: + VGM_LOG("BITREADER: read fail\n"); + *value = 0; + return 0; +} + +#ifndef BITSTREAM_READ_ONLY +/* Write bits (max 32) to buf and update the bit offset. Order is BE (MSF). */ +static int wb_bits(bitstream_t* ob, uint32_t bits, uint32_t value) { + uint32_t shift, pos; + int i, bit_val, bit_buf; + + if (bits > 32 || ob->b_off + bits > ob->bufsize * 8) + goto fail; + + pos = ob->b_off / 8; /* byte offset */ + shift = ob->b_off % 8; /* bit sub-offset */ + + for (i = 0; i < bits; i++) { + bit_val = (1U << (bits-1-i)); /* bit check for value */ + bit_buf = (1U << (8-1-shift)) & 0xFF; /* bit to set in buf */ + + if (value & bit_val) /* is bit in val set? */ + ob->buf[pos] |= bit_buf; /* set bit */ + else + ob->buf[pos] &= ~bit_buf; /* unset bit */ + + shift++; + if (shift % 8 == 0) { /* new byte starts */ + shift = 0; + pos++; + } + } + + ob->b_off += bits; + return 1; +fail: + return 0; +} +#endif + +#endif diff --git a/src/coding/mpeg_custom_utils_ealayer3.c b/src/coding/mpeg_custom_utils_ealayer3.c index ecebf499..4f0f19b4 100644 --- a/src/coding/mpeg_custom_utils_ealayer3.c +++ b/src/coding/mpeg_custom_utils_ealayer3.c @@ -1,4 +1,5 @@ #include "mpeg_decoder.h" +#include "mpeg_bitreader.h" #ifdef VGM_USE_MPEG @@ -27,9 +28,9 @@ /* helper to pass around */ typedef struct { - STREAMFILE *sf; + STREAMFILE* sf; off_t offset; - vgm_bitstream is; + bitstream_t is; uint8_t buf[EALAYER3_MAX_EA_FRAME_SIZE]; int leftover_bits; } ealayer3_buffer_t; @@ -82,21 +83,21 @@ typedef struct { } ealayer3_frame_t; -static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, ealayer3_buffer_t *ib, ealayer3_frame_t *eaf); -static int ealayer3_parse_frame_v1(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf, int channels_per_frame, int is_v1b); -static int ealayer3_parse_frame_v2(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf); -static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf); -static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *eaf_0, vgm_bitstream *is_1, ealayer3_frame_t *eaf_1, vgm_bitstream *os); -static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_t *eaf); -static int ealayer3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start); -static int ealayer3_is_empty_frame_v2p(STREAMFILE *sf, off_t offset); +static int ealayer3_parse_frame(mpeg_codec_data* data, int num_stream, ealayer3_buffer_t* ib, ealayer3_frame_t* eaf); +static int ealayer3_parse_frame_v1(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf, int channels_per_frame, int is_v1b); +static int ealayer3_parse_frame_v2(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf); +static int ealayer3_parse_frame_common(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf); +static int ealayer3_rebuild_mpeg_frame(bitstream_t* is_0, ealayer3_frame_t* eaf_0, bitstream_t* is_1, ealayer3_frame_t* eaf_1, bitstream_t* os); +static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream, ealayer3_frame_t* eaf); +static int ealayer3_skip_data(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream, int at_start); +static int ealayer3_is_empty_frame_v2p(STREAMFILE* sf, off_t offset); /* **************************************************************************** */ /* EXTERNAL API */ /* **************************************************************************** */ /* init codec from an EALayer3 frame */ -int mpeg_custom_setup_init_ealayer3(STREAMFILE *streamfile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) { +int mpeg_custom_setup_init_ealayer3(STREAMFILE* streamfile, off_t start_offset, mpeg_codec_data* data, coding_t *coding_type) { int ok; ealayer3_buffer_t ib = {0}; ealayer3_frame_t eaf; @@ -130,7 +131,7 @@ fail: } /* writes data to the buffer and moves offsets, transforming EALayer3 frames */ -int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) { +int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream) { mpeg_custom_stream *ms = data->streams[num_stream]; int ok, granule_found; ealayer3_buffer_t ib_0 = {0}, ib_1 = {0}; @@ -148,7 +149,7 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data * /* read first frame/granule, or PCM-only frame (found alone at the end of SCHl streams) */ - { + { //;VGM_LOG("s%i: get granule0 at %lx\n", num_stream,stream->offset); if (!ealayer3_skip_data(stream, data, num_stream, 1)) goto fail; @@ -168,15 +169,15 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data * if (!ealayer3_skip_data(stream, data, num_stream, 0)) goto fail; - } + } - /* In EAL3 V2P sometimes there is a new SNS/SPS block between granules. Instead of trying to fix it here - * or in blocked layout (too complex/patchy), SNS/SPS uses a custom streamfile that simply removes all - * block headers, so this parser only sees raw EALayer3 data. It also discards samples, which confuses - * blocked layout calculations - * - * Similarly (as V2P decodes and writes 1 granule at a time) stream can end in granule0. Since mpg123 - * decodes in pairs we detect and feed it a fake end granule1, to get the last granule0's 576 samples. */ + /* In EAL3 V2P sometimes there is a new SNS/SPS block between granules. Instead of trying to fix it here + * or in blocked layout (too complex/patchy), SNS/SPS uses a custom streamfile that simply removes all + * block headers, so this parser only sees raw EALayer3 data. It also discards samples, which confuses + * blocked layout calculations + * + * Similarly (as V2P decodes and writes 1 granule at a time) stream can end in granule0. Since mpg123 + * decodes in pairs we detect and feed it a fake end granule1, to get the last granule0's 576 samples. */ granule_found = 0; /* get second frame/granule (MPEG1 only) if first granule was found */ @@ -219,10 +220,9 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data * /* rebuild EALayer3 frame to MPEG frame */ { - vgm_bitstream os = {0}; + bitstream_t os = {0}; - os.buf = ms->buffer; - os.bufsize = ms->buffer_size; + init_bitstream(&os, ms->buffer, ms->buffer_size); ok = ealayer3_rebuild_mpeg_frame(&ib_0.is, &eaf_0, &ib_1.is, &eaf_1, &os); if (!ok) goto fail; @@ -243,7 +243,7 @@ fail: /* Read at most N bits from streamfile. This makes more smaller reads (not good) but * allows exact frame size reading (good), as reading over a frame then reading back * is expensive in EALayer3 since it uses custom streamfiles. */ -static void fill_buf(ealayer3_buffer_t *ib, int bits) { +static void fill_buf(ealayer3_buffer_t* ib, int bits) { size_t read_size, bytes_size; int mod; @@ -273,7 +273,7 @@ static void fill_buf(ealayer3_buffer_t *ib, int bits) { ib->leftover_bits = (mod > 0 ? 8 - mod : 0); } -static int ealayer3_parse_frame(mpeg_codec_data *data, int num_stream, ealayer3_buffer_t *ib, ealayer3_frame_t *eaf) { +static int ealayer3_parse_frame(mpeg_codec_data* data, int num_stream, ealayer3_buffer_t* ib, ealayer3_frame_t* eaf) { int ok; /* We must pass this from state, as not all EA-frames have channel info. @@ -302,14 +302,14 @@ fail: /* read V1"a" (in SCHl) and V1"b" (in SNS) EALayer3 frame */ -static int ealayer3_parse_frame_v1(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf, int channels_per_frame, int is_v1b) { +static int ealayer3_parse_frame_v1(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf, int channels_per_frame, int is_v1b) { int ok; - vgm_bitstream *is = &ib->is; + bitstream_t* is = &ib->is; /* read EA-frame V1 header */ fill_buf(ib, 8); - r_bits(is, 8,&eaf->v1_pcm_flag); + rb_bits(is, 8,&eaf->v1_pcm_flag); eaf->pre_size = 1; /* 8b */ @@ -331,15 +331,15 @@ static int ealayer3_parse_frame_v1(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf, /* check PCM block */ if (eaf->v1_pcm_flag == 0xEE) { fill_buf(ib, 32); - r_bits(is, 16,&eaf->v1_offset_samples); /* PCM block offset in the buffer */ - r_bits(is, 16,&eaf->v1_pcm_samples); /* number of PCM samples, can be 0 */ + rb_bits(is, 16,&eaf->v1_offset_samples); /* PCM block offset in the buffer */ + rb_bits(is, 16,&eaf->v1_pcm_samples); /* number of PCM samples, can be 0 */ eaf->pre_size += 2+2; /* 16b+16b */ eaf->pcm_size = (2*eaf->v1_pcm_samples * channels_per_frame); if (is_v1b) { /* extra 32b in v1b */ fill_buf(ib, 32); - r_bits(is, 32,&eaf->v1_pcm_unknown); + rb_bits(is, 32,&eaf->v1_pcm_unknown); eaf->pre_size += 4; /* 32b */ @@ -356,26 +356,26 @@ fail: /* read V2"PCM" and V2"Spike" EALayer3 frame (exactly the same but V2P seems to have bigger * PCM blocks and maybe smaller frames) */ -static int ealayer3_parse_frame_v2(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf) { +static int ealayer3_parse_frame_v2(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf) { int ok; - vgm_bitstream *is = &ib->is; + bitstream_t* is = &ib->is; /* read EA-frame V2 header */ fill_buf(ib, 16); - r_bits(is, 1,&eaf->v2_extended_flag); - r_bits(is, 1,&eaf->v2_stereo_flag); - r_bits(is, 2,&eaf->v2_reserved); - r_bits(is, 12,&eaf->v2_frame_size); + rb_bits(is, 1,&eaf->v2_extended_flag); + rb_bits(is, 1,&eaf->v2_stereo_flag); + rb_bits(is, 2,&eaf->v2_reserved); + rb_bits(is, 12,&eaf->v2_frame_size); eaf->pre_size = 2; /* 16b */ if (eaf->v2_extended_flag) { fill_buf(ib, 32); - r_bits(is, 2,&eaf->v2_offset_mode); - r_bits(is, 10,&eaf->v2_offset_samples); - r_bits(is, 10,&eaf->v2_pcm_samples); - r_bits(is, 10,&eaf->v2_common_size); + rb_bits(is, 2,&eaf->v2_offset_mode); + rb_bits(is, 10,&eaf->v2_offset_samples); + rb_bits(is, 10,&eaf->v2_pcm_samples); + rb_bits(is, 10,&eaf->v2_common_size); eaf->pre_size += 4; /* 32b */ } @@ -406,7 +406,7 @@ fail: /* parses an EALayer3 frame (common part) */ -static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf) { +static int ealayer3_parse_frame_common(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf) { /* index tables */ static const int versions[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 }; static const int sample_rates[4][4] = { /* [version_index][sample rate index] */ @@ -417,17 +417,17 @@ static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t * }; static const int channels[4] = { 2,2,2, 1 }; /* [channel_mode] */ - vgm_bitstream *is = &ib->is; + bitstream_t* is = &ib->is; off_t start_b_off = is->b_off; int i, fill_bits, others_2_bits; /* read main header */ fill_buf(ib, 8); - r_bits(is, 2,&eaf->version_index); - r_bits(is, 2,&eaf->sample_rate_index); - r_bits(is, 2,&eaf->channel_mode); - r_bits(is, 2,&eaf->mode_extension); + rb_bits(is, 2,&eaf->version_index); + rb_bits(is, 2,&eaf->sample_rate_index); + rb_bits(is, 2,&eaf->channel_mode); + rb_bits(is, 2,&eaf->mode_extension); /* check empty frame */ @@ -455,7 +455,7 @@ static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t * /* read side info */ fill_buf(ib, 1); - r_bits(is, 1,&eaf->granule_index); + rb_bits(is, 1,&eaf->granule_index); fill_bits = (eaf->mpeg1 && eaf->granule_index == 1) ? 4 * eaf->channels : 0; fill_bits = fill_bits + (12 + 32 + others_2_bits) * eaf->channels; @@ -463,15 +463,15 @@ static int ealayer3_parse_frame_common(ealayer3_buffer_t *ib, ealayer3_frame_t * if (eaf->mpeg1 && eaf->granule_index == 1) { for (i = 0; i < eaf->channels; i++) { - r_bits(is, 4,&eaf->scfsi[i]); + rb_bits(is, 4,&eaf->scfsi[i]); } } for (i = 0; i < eaf->channels; i++) { - r_bits(is, 12,&eaf->main_data_size[i]); + rb_bits(is, 12,&eaf->main_data_size[i]); /* divided in 47b=32+15 (MPEG1) or 51b=32+19 (MPEG2), arbitrarily */ - r_bits(is, 32,&eaf->others_1[i]); - r_bits(is, others_2_bits,&eaf->others_2[i]); + rb_bits(is, 32,&eaf->others_1[i]); + rb_bits(is, others_2_bits,&eaf->others_2[i]); } @@ -496,7 +496,7 @@ fail: /* converts an EALAYER3 frame to a standard MPEG frame from pre-parsed info */ -static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *eaf_0, vgm_bitstream *is_1, ealayer3_frame_t *eaf_1, vgm_bitstream* os) { +static int ealayer3_rebuild_mpeg_frame(bitstream_t* is_0, ealayer3_frame_t* eaf_0, bitstream_t* is_1, ealayer3_frame_t* eaf_1, bitstream_t* os) { uint32_t c = 0; int i, j; int expected_bitrate_index, expected_frame_size; @@ -537,55 +537,55 @@ static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *ea #endif /* write MPEG1/2 frame header */ - w_bits(os, 11, 0x7FF); /* sync */ - w_bits(os, 2, eaf_0->version_index); - w_bits(os, 2, 0x01); /* layer III index */ - w_bits(os, 1, 1); /* "no CRC" flag */ - w_bits(os, 4, expected_bitrate_index); - w_bits(os, 2, eaf_0->sample_rate_index); - w_bits(os, 1, 0); /* padding */ - w_bits(os, 1, 0); /* private */ - w_bits(os, 2, eaf_0->channel_mode); - w_bits(os, 2, eaf_0->mode_extension); - w_bits(os, 1, 1); /* copyrighted */ - w_bits(os, 1, 1); /* original */ - w_bits(os, 2, 0); /* emphasis */ + wb_bits(os, 11, 0x7FF); /* sync */ + wb_bits(os, 2, eaf_0->version_index); + wb_bits(os, 2, 0x01); /* layer III index */ + wb_bits(os, 1, 1); /* "no CRC" flag */ + wb_bits(os, 4, expected_bitrate_index); + wb_bits(os, 2, eaf_0->sample_rate_index); + wb_bits(os, 1, 0); /* padding */ + wb_bits(os, 1, 0); /* private */ + wb_bits(os, 2, eaf_0->channel_mode); + wb_bits(os, 2, eaf_0->mode_extension); + wb_bits(os, 1, 1); /* copyrighted */ + wb_bits(os, 1, 1); /* original */ + wb_bits(os, 2, 0); /* emphasis */ if (eaf_0->mpeg1) { int private_bits = (eaf_0->channels==1 ? 5 : 3); /* write MPEG1 side info */ - w_bits(os, 9, 0); /* main data start (no bit reservoir) */ - w_bits(os, private_bits, 0); + wb_bits(os, 9, 0); /* main data start (no bit reservoir) */ + wb_bits(os, private_bits, 0); for (i = 0; i < eaf_1->channels; i++) { - w_bits(os, 4, eaf_1->scfsi[i]); /* saved in granule1 only */ + wb_bits(os, 4, eaf_1->scfsi[i]); /* saved in granule1 only */ } for (i = 0; i < eaf_0->channels; i++) { /* granule0 */ - w_bits(os, 12, eaf_0->main_data_size[i]); - w_bits(os, 32, eaf_0->others_1[i]); - w_bits(os, 47-32, eaf_0->others_2[i]); + wb_bits(os, 12, eaf_0->main_data_size[i]); + wb_bits(os, 32, eaf_0->others_1[i]); + wb_bits(os, 47-32, eaf_0->others_2[i]); } for (i = 0; i < eaf_1->channels; i++) { /* granule1 */ - w_bits(os, 12, eaf_1->main_data_size[i]); - w_bits(os, 32, eaf_1->others_1[i]); - w_bits(os, 47-32, eaf_1->others_2[i]); + wb_bits(os, 12, eaf_1->main_data_size[i]); + wb_bits(os, 32, eaf_1->others_1[i]); + wb_bits(os, 47-32, eaf_1->others_2[i]); } /* write MPEG1 main data */ is_0->b_off = eaf_0->data_offset_b; for (i = 0; i < eaf_0->channels; i++) { /* granule0 */ for (j = 0; j < eaf_0->main_data_size[i]; j++) { - r_bits(is_0, 1, &c); - w_bits(os, 1, c); + rb_bits(is_0, 1, &c); + wb_bits(os, 1, c); } } is_1->b_off = eaf_1->data_offset_b; for (i = 0; i < eaf_1->channels; i++) { /* granule1 */ for (j = 0; j < eaf_1->main_data_size[i]; j++) { - r_bits(is_1, 1, &c); - w_bits(os, 1, c); + rb_bits(is_1, 1, &c); + wb_bits(os, 1, c); } } } @@ -593,21 +593,21 @@ static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *ea int private_bits = (eaf_0->channels==1 ? 1 : 2); /* write MPEG2 side info */ - w_bits(os, 8, 0); /* main data start (no bit reservoir) */ - w_bits(os, private_bits, 0); + wb_bits(os, 8, 0); /* main data start (no bit reservoir) */ + wb_bits(os, private_bits, 0); for (i = 0; i < eaf_0->channels; i++) { - w_bits(os, 12, eaf_0->main_data_size[i]); - w_bits(os, 32, eaf_0->others_1[i]); - w_bits(os, 51-32, eaf_0->others_2[i]); + wb_bits(os, 12, eaf_0->main_data_size[i]); + wb_bits(os, 32, eaf_0->others_1[i]); + wb_bits(os, 51-32, eaf_0->others_2[i]); } /* write MPEG2 main data */ is_0->b_off = eaf_0->data_offset_b; for (i = 0; i < eaf_0->channels; i++) { for (j = 0; j < eaf_0->main_data_size[i]; j++) { - r_bits(is_0, 1, &c); - w_bits(os, 1, c); + rb_bits(is_0, 1, &c); + wb_bits(os, 1, c); } } } @@ -615,7 +615,7 @@ static int ealayer3_rebuild_mpeg_frame(vgm_bitstream *is_0, ealayer3_frame_t *ea /* align to closest 8b */ if (os->b_off % 8) { int align_bits = 8 - (os->b_off % 8); - w_bits(os, align_bits, 0); + wb_bits(os, align_bits, 0); } @@ -636,7 +636,7 @@ fail: return 0; } -static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_number, int channels_per_frame, int is_packed, STREAMFILE *sf) { +static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_number, int channels_per_frame, int is_packed, STREAMFILE* sf) { int i, ch; uint8_t pcm_block[1152 * 2 * 2]; /* assumed max: 1 MPEG frame samples * 16b * max channels */ size_t pcm_size = pcm_number * 2 * channels_per_frame; @@ -687,7 +687,7 @@ static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_n /* write PCM block directly to sample buffer and setup decode discard (EALayer3 seems to use this as a prefetch of sorts). * Seems to alter decoded sample buffer to handle encoder delay/padding in a twisted way. */ -static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_t *eaf) { +static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream, ealayer3_frame_t* eaf) { mpeg_custom_stream *ms = data->streams[num_stream]; int channels_per_frame = ms->channels_per_frame; size_t bytes_filled; @@ -817,7 +817,7 @@ fail: * * EALayer3 v1 in SCHl uses external offsets and 1ch multichannel instead. */ -static int ealayer3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start) { +static int ealayer3_skip_data(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream, int at_start) { int ok, i; ealayer3_buffer_t ib = {0}; ealayer3_frame_t eaf; @@ -845,7 +845,7 @@ fail: return 0; } -static int ealayer3_is_empty_frame_v2p(STREAMFILE *sf, off_t offset) { +static int ealayer3_is_empty_frame_v2p(STREAMFILE* sf, off_t offset) { /* V2P frame header should contain a valid frame size (lower 12b) */ uint16_t v2_header = read_u16be(offset, sf); return (v2_header % 0xFFF) == 0 || v2_header == 0xFFFF; diff --git a/src/coding/vorbis_bitreader.h b/src/coding/vorbis_bitreader.h new file mode 100644 index 00000000..d17c87e5 --- /dev/null +++ b/src/coding/vorbis_bitreader.h @@ -0,0 +1,103 @@ +#ifndef _VORBIS_BITREADER_H +#define _VORBIS_BITREADER_H + +/* Simple bitreader for Vorbis' bit format. + * Kept in .h since it's slightly faster (compiler can optimize statics better) */ + + +typedef struct { + uint8_t* buf; /* buffer to read/write */ + size_t bufsize; /* max size of the buffer */ + uint32_t b_off; /* current offset in bits inside the buffer */ +} bitstream_t; + +/* convenience util */ +static void init_bitstream(bitstream_t* b, uint8_t* buf, size_t bufsize) { + b->buf = buf; + b->bufsize = bufsize; + b->b_off = 0; +} + +/* same as (1 << bits) - 1, but that seems to trigger some nasty UB when bits = 32 + * (though in theory (1 << 32) = 0, 0 - 1 = UINT_MAX, but gives 0 compiling in some cases, but not always) */ +static const uint32_t MASK_TABLE[33] = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, + 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff, 0x0001ffff, + 0x0003ffff, 0x0007ffff, 0x000fffff, 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff, 0x01ffffff, 0x03ffffff, + 0x07ffffff, 0x0fffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff +}; + +/* Read bits (max 32) from buf and update the bit offset. Vorbis packs values in LSB order and byte by byte. + * (ex. from 2 bytes 00100111 00000001 we can could read 4b=0111 and 6b=010010, 6b=remainder (second value is split into the 2nd byte) */ +static int rv_bits(bitstream_t* ib, uint32_t bits, uint32_t* value) { + uint32_t shift, mask, pos, val; + + if (bits > 32 || ib->b_off + bits > ib->bufsize * 8) + goto fail; + + pos = ib->b_off / 8; /* byte offset */ + shift = ib->b_off % 8; /* bit sub-offset */ + mask = MASK_TABLE[bits]; /* to remove upper in highest byte */ + + val = ib->buf[pos+0] >> shift; + if (bits + shift > 8) { + val |= ib->buf[pos+1] << (8u - shift); + if (bits + shift > 16) { + val |= ib->buf[pos+2] << (16u - shift); + if (bits + shift > 24) { + val |= ib->buf[pos+3] << (24u - shift); + if (bits + shift > 32) { + val |= ib->buf[pos+4] << (32u - shift); /* upper bits are lost (shifting over 32) */ + } + } + } + } + + *value = (val & mask); + + ib->b_off += bits; + + return 1; +fail: + VGM_LOG("BITREADER: read fail\n"); + *value = 0; + return 0; +} + +#ifndef BITSTREAM_READ_ONLY +/* Write bits (max 32) to buf and update the bit offset. Vorbis packs values in LSB order and byte by byte. + * (ex. writing 1101011010 from b_off 2 we get 01101011 00001101 (value split, and 11 in the first byte skipped)*/ +static int wv_bits(bitstream_t* ob, uint32_t bits, uint32_t value) { + uint32_t shift, mask, pos; + + if (bits > 32 || ob->b_off + bits > ob->bufsize*8) + goto fail; + + pos = ob->b_off / 8; /* byte offset */ + shift = ob->b_off % 8; /* bit sub-offset */ + mask = (1 << shift) - 1; /* to keep lower bits in lowest byte */ + + ob->buf[pos+0] = (value << shift) | (ob->buf[pos+0] & mask); + if (bits + shift > 8) { + ob->buf[pos+1] = value >> (8 - shift); + if (bits + shift > 16) { + ob->buf[pos+2] = value >> (16 - shift); + if (bits + shift > 24) { + ob->buf[pos+3] = value >> (24 - shift); + if (bits + shift > 32) { + /* upper bits are set to 0 (shifting unsigned) but shouldn't matter */ + ob->buf[pos+4] = value >> (32 - shift); + } + } + } + } + + ob->b_off += bits; + return 1; +fail: + VGM_LOG("BITREADER: write fail\n"); + return 0; +} +#endif + +#endif diff --git a/src/coding/vorbis_custom_decoder.c b/src/coding/vorbis_custom_decoder.c index d04d199f..4b887a46 100644 --- a/src/coding/vorbis_custom_decoder.c +++ b/src/coding/vorbis_custom_decoder.c @@ -1,230 +1,228 @@ -#include -#include "coding.h" -#include "vorbis_custom_decoder.h" - -#ifdef VGM_USE_VORBIS -#include - -#define VORBIS_DEFAULT_BUFFER_SIZE 0x8000 /* should be at least the size of the setup header, ~0x2000 */ - -static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples_to_do, float ** pcm); - -/** - * Inits a vorbis stream of some custom variety. - * - * Normally Vorbis packets are stored in .ogg, which is divided into OggS pages/packets, and the first packets contain necessary - * Vorbis setup. For custom vorbis the OggS layer is replaced/optimized, the setup can be modified or stored elsewhere - * (i.e.- in the .exe) and raw Vorbis packets may be modified as well, presumably to shave off some kb and/or obfuscate. - * We'll manually read/modify the data and decode it with libvorbis calls. - * - * Reference: https://www.xiph.org/vorbis/doc/libvorbis/overview.html - */ -vorbis_custom_codec_data * init_vorbis_custom(STREAMFILE *streamFile, off_t start_offset, vorbis_custom_t type, vorbis_custom_config * config) { - vorbis_custom_codec_data * data = NULL; - int ok; - - /* init stuff */ - data = calloc(1,sizeof(vorbis_custom_codec_data)); - if (!data) goto fail; - - data->buffer_size = VORBIS_DEFAULT_BUFFER_SIZE; - data->buffer = calloc(sizeof(uint8_t), data->buffer_size); - if (!data->buffer) goto fail; - - /* keep around to decode too */ - data->type = type; - memcpy(&data->config, config, sizeof(vorbis_custom_config)); - - - /* init vorbis stream state, using 3 fake Ogg setup packets (info, comments, setup/codebooks) - * libvorbis expects parsed Ogg pages, but we'll fake them with our raw data instead */ - vorbis_info_init(&data->vi); - vorbis_comment_init(&data->vc); - - data->op.packet = data->buffer; - data->op.b_o_s = 1; /* fake headers start */ - - /* init header */ - switch(data->type) { - case VORBIS_FSB: ok = vorbis_custom_setup_init_fsb(streamFile, start_offset, data); break; - case VORBIS_WWISE: ok = vorbis_custom_setup_init_wwise(streamFile, start_offset, data); break; - case VORBIS_OGL: ok = vorbis_custom_setup_init_ogl(streamFile, start_offset, data); break; - case VORBIS_SK: ok = vorbis_custom_setup_init_sk(streamFile, start_offset, data); break; - case VORBIS_VID1: ok = vorbis_custom_setup_init_vid1(streamFile, start_offset, data); break; - default: goto fail; - } - if(!ok) goto fail; - - data->op.b_o_s = 0; /* end of fake headers */ - - /* init vorbis global and block state */ - if (vorbis_synthesis_init(&data->vd,&data->vi) != 0) goto fail; - if (vorbis_block_init(&data->vd,&data->vb) != 0) goto fail; - - - /* write output */ - config->data_start_offset = data->config.data_start_offset; - - - return data; - -fail: - VGM_LOG("VORBIS: init fail at around 0x%x\n", (uint32_t)start_offset); - free_vorbis_custom(data); - return NULL; -} - -/* Decodes Vorbis packets into a libvorbis sample buffer, and copies them to outbuf */ -void decode_vorbis_custom(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels) { - VGMSTREAMCHANNEL *stream = &vgmstream->ch[0]; - vorbis_custom_codec_data * data = vgmstream->codec_data; - size_t stream_size = get_streamfile_size(stream->streamfile); - //data->op.packet = data->buffer;/* implicit from init */ - int samples_done = 0; - - while (samples_done < samples_to_do) { - - /* extra EOF check for edge cases */ - if (stream->offset >= stream_size) { - memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample) * channels); - break; - } - - - if (data->samples_full) { /* read more samples */ - int samples_to_get; - float **pcm; - - /* get PCM samples from libvorbis buffers */ - samples_to_get = vorbis_synthesis_pcmout(&data->vd, &pcm); - if (!samples_to_get) { - data->samples_full = 0; /* request more if empty*/ - continue; - } - - if (data->samples_to_discard) { - /* discard samples for looping */ - if (samples_to_get > data->samples_to_discard) - samples_to_get = data->samples_to_discard; - data->samples_to_discard -= samples_to_get; - } - else { - /* get max samples and convert from Vorbis float pcm to 16bit pcm */ - if (samples_to_get > samples_to_do - samples_done) - samples_to_get = samples_to_do - samples_done; - pcm_convert_float_to_16(data->vi.channels, outbuf + samples_done * channels, samples_to_get, pcm); - samples_done += samples_to_get; - } - - /* mark consumed samples from the buffer - * (non-consumed samples are returned in next vorbis_synthesis_pcmout calls) */ - vorbis_synthesis_read(&data->vd, samples_to_get); - } - else { /* read more data */ - int ok, rc; - - /* not actually needed, but feels nicer */ - data->op.granulepos += samples_to_do; /* can be changed next if desired */ - data->op.packetno++; - - /* read/transform data into the ogg_packet buffer and advance offsets */ - switch(data->type) { - case VORBIS_FSB: ok = vorbis_custom_parse_packet_fsb(stream, data); break; - case VORBIS_WWISE: ok = vorbis_custom_parse_packet_wwise(stream, data); break; - case VORBIS_OGL: ok = vorbis_custom_parse_packet_ogl(stream, data); break; - case VORBIS_SK: ok = vorbis_custom_parse_packet_sk(stream, data); break; - case VORBIS_VID1: ok = vorbis_custom_parse_packet_vid1(stream, data); break; - default: goto decode_fail; - } - if(!ok) { - goto decode_fail; - } - - - /* parse the fake ogg packet into a logical vorbis block */ - rc = vorbis_synthesis(&data->vb,&data->op); - if (rc == OV_ENOTAUDIO) { - VGM_LOG("Vorbis: not an audio packet (size=0x%x) @ %x\n",(size_t)data->op.bytes,(uint32_t)stream->offset); - //VGM_LOGB(data->op.packet, (size_t)data->op.bytes,0); - continue; /* rarely happens, seems ok? */ - } else if (rc != 0) goto decode_fail; - - /* finally decode the logical block into samples */ - rc = vorbis_synthesis_blockin(&data->vd,&data->vb); - if (rc != 0) goto decode_fail; /* ? */ - - - data->samples_full = 1; - } - } - - return; - -decode_fail: - /* on error just put some 0 samples */ - VGM_LOG("VORBIS: decode fail at %x, missing %i samples\n", (uint32_t)stream->offset, (samples_to_do - samples_done)); - memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * channels * sizeof(sample)); -} - -/* converts from internal Vorbis format to standard PCM (mostly from Xiph's decoder_example.c) */ -static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples_to_do, float ** pcm) { - int ch, s; - sample_t *ptr; - float *channel; - - /* convert float PCM (multichannel float array, with pcm[0]=ch0, pcm[1]=ch1, pcm[2]=ch0, etc) - * to 16 bit signed PCM ints (host order) and interleave + fix clipping */ - for (ch = 0; ch < channels; ch++) { - /* channels should be in standard order unlike Ogg Vorbis (at least in FSB) */ - ptr = outbuf + ch; - channel = pcm[ch]; - for (s = 0; s < samples_to_do; s++) { - int val = (int)floor(channel[s] * 32767.0f + 0.5f); - if (val > 32767) val = 32767; - else if (val < -32768) val = -32768; - - *ptr = val; - ptr += channels; - } - } -} - -/* ********************************************** */ - -void free_vorbis_custom(vorbis_custom_codec_data * data) { - if (!data) - return; - - /* internal decoder cleanup */ - vorbis_block_clear(&data->vb); - vorbis_dsp_clear(&data->vd); - vorbis_comment_clear(&data->vc); - vorbis_info_clear(&data->vi); - - free(data->buffer); - free(data); -} - -void reset_vorbis_custom(VGMSTREAM *vgmstream) { - vorbis_custom_codec_data *data = vgmstream->codec_data; - if (!data) return; - - /* Seeking is provided by the Ogg layer, so with custom vorbis we'd need seek tables instead. - * To avoid having to parse different formats we'll just discard until the expected sample */ - vorbis_synthesis_restart(&data->vd); - data->samples_to_discard = 0; -} - -void seek_vorbis_custom(VGMSTREAM *vgmstream, int32_t num_sample) { - vorbis_custom_codec_data *data = vgmstream->codec_data; - if (!data) return; - - /* Seeking is provided by the Ogg layer, so with custom vorbis we'd need seek tables instead. - * To avoid having to parse different formats we'll just discard until the expected sample */ - vorbis_synthesis_restart(&data->vd); - data->samples_to_discard = num_sample; - if (vgmstream->loop_ch) - vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset; -} - -#endif +#include +#include "coding.h" +#include "vorbis_custom_decoder.h" + +#ifdef VGM_USE_VORBIS +#include + +#define VORBIS_DEFAULT_BUFFER_SIZE 0x8000 /* should be at least the size of the setup header, ~0x2000 */ + +static void pcm_convert_float_to_16(sample_t* outbuf, int samples_to_do, float** pcm, int channels); + +/** + * Inits a vorbis stream of some custom variety. + * + * Normally Vorbis packets are stored in .ogg, which is divided into OggS pages/packets, and the first packets contain necessary + * Vorbis setup. For custom vorbis the OggS layer is replaced/optimized, the setup can be modified or stored elsewhere + * (i.e.- in the .exe) and raw Vorbis packets may be modified as well, presumably to shave off some kb and/or obfuscate. + * We'll manually read/modify the data and decode it with libvorbis calls. + * + * Reference: https://www.xiph.org/vorbis/doc/libvorbis/overview.html + */ +vorbis_custom_codec_data* init_vorbis_custom(STREAMFILE* sf, off_t start_offset, vorbis_custom_t type, vorbis_custom_config* config) { + vorbis_custom_codec_data* data = NULL; + int ok; + + /* init stuff */ + data = calloc(1,sizeof(vorbis_custom_codec_data)); + if (!data) goto fail; + + data->buffer_size = VORBIS_DEFAULT_BUFFER_SIZE; + data->buffer = calloc(sizeof(uint8_t), data->buffer_size); + if (!data->buffer) goto fail; + + /* keep around to decode too */ + data->type = type; + memcpy(&data->config, config, sizeof(vorbis_custom_config)); + + + /* init vorbis stream state, using 3 fake Ogg setup packets (info, comments, setup/codebooks) + * libvorbis expects parsed Ogg pages, but we'll fake them with our raw data instead */ + vorbis_info_init(&data->vi); + vorbis_comment_init(&data->vc); + + data->op.packet = data->buffer; + data->op.b_o_s = 1; /* fake headers start */ + + /* init header */ + switch(data->type) { + case VORBIS_FSB: ok = vorbis_custom_setup_init_fsb(sf, start_offset, data); break; + case VORBIS_WWISE: ok = vorbis_custom_setup_init_wwise(sf, start_offset, data); break; + case VORBIS_OGL: ok = vorbis_custom_setup_init_ogl(sf, start_offset, data); break; + case VORBIS_SK: ok = vorbis_custom_setup_init_sk(sf, start_offset, data); break; + case VORBIS_VID1: ok = vorbis_custom_setup_init_vid1(sf, start_offset, data); break; + default: goto fail; + } + if(!ok) goto fail; + + data->op.b_o_s = 0; /* end of fake headers */ + + /* init vorbis global and block state */ + if (vorbis_synthesis_init(&data->vd,&data->vi) != 0) goto fail; + if (vorbis_block_init(&data->vd,&data->vb) != 0) goto fail; + + + /* write output */ + config->data_start_offset = data->config.data_start_offset; + + + return data; + +fail: + VGM_LOG("VORBIS: init fail at around 0x%x\n", (uint32_t)start_offset); + free_vorbis_custom(data); + return NULL; +} + +/* Decodes Vorbis packets into a libvorbis sample buffer, and copies them to outbuf */ +void decode_vorbis_custom(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do, int channels) { + VGMSTREAMCHANNEL *stream = &vgmstream->ch[0]; + vorbis_custom_codec_data* data = vgmstream->codec_data; + size_t stream_size = get_streamfile_size(stream->streamfile); + //data->op.packet = data->buffer;/* implicit from init */ + int samples_done = 0; + + while (samples_done < samples_to_do) { + + /* extra EOF check for edge cases */ + if (stream->offset >= stream_size) { + memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * sizeof(sample) * channels); + break; + } + + + if (data->samples_full) { /* read more samples */ + int samples_to_get; + float **pcm; + + /* get PCM samples from libvorbis buffers */ + samples_to_get = vorbis_synthesis_pcmout(&data->vd, &pcm); + if (!samples_to_get) { + data->samples_full = 0; /* request more if empty*/ + continue; + } + + if (data->samples_to_discard) { + /* discard samples for looping */ + if (samples_to_get > data->samples_to_discard) + samples_to_get = data->samples_to_discard; + data->samples_to_discard -= samples_to_get; + } + else { + /* get max samples and convert from Vorbis float pcm to 16bit pcm */ + if (samples_to_get > samples_to_do - samples_done) + samples_to_get = samples_to_do - samples_done; + pcm_convert_float_to_16(outbuf + samples_done * channels, samples_to_get, pcm, data->vi.channels); + samples_done += samples_to_get; + } + + /* mark consumed samples from the buffer + * (non-consumed samples are returned in next vorbis_synthesis_pcmout calls) */ + vorbis_synthesis_read(&data->vd, samples_to_get); + } + else { /* read more data */ + int ok, rc; + + /* not actually needed, but feels nicer */ + data->op.granulepos += samples_to_do; /* can be changed next if desired */ + data->op.packetno++; + + /* read/transform data into the ogg_packet buffer and advance offsets */ + switch(data->type) { + case VORBIS_FSB: ok = vorbis_custom_parse_packet_fsb(stream, data); break; + case VORBIS_WWISE: ok = vorbis_custom_parse_packet_wwise(stream, data); break; + case VORBIS_OGL: ok = vorbis_custom_parse_packet_ogl(stream, data); break; + case VORBIS_SK: ok = vorbis_custom_parse_packet_sk(stream, data); break; + case VORBIS_VID1: ok = vorbis_custom_parse_packet_vid1(stream, data); break; + default: goto decode_fail; + } + if(!ok) { + goto decode_fail; + } + + + /* parse the fake ogg packet into a logical vorbis block */ + rc = vorbis_synthesis(&data->vb,&data->op); + if (rc == OV_ENOTAUDIO) { + VGM_LOG("Vorbis: not an audio packet (size=0x%x) @ %x\n",(size_t)data->op.bytes,(uint32_t)stream->offset); + //VGM_LOGB(data->op.packet, (size_t)data->op.bytes,0); + continue; /* rarely happens, seems ok? */ + } else if (rc != 0) goto decode_fail; + + /* finally decode the logical block into samples */ + rc = vorbis_synthesis_blockin(&data->vd,&data->vb); + if (rc != 0) goto decode_fail; /* ? */ + + + data->samples_full = 1; + } + } + + return; + +decode_fail: + /* on error just put some 0 samples */ + VGM_LOG("VORBIS: decode fail at %x, missing %i samples\n", (uint32_t)stream->offset, (samples_to_do - samples_done)); + memset(outbuf + samples_done * channels, 0, (samples_to_do - samples_done) * channels * sizeof(sample)); +} + +/* converts from internal Vorbis format to standard PCM (mostly from Xiph's decoder_example.c) */ +static void pcm_convert_float_to_16(sample_t* outbuf, int samples_to_do, float** pcm, int channels) { + int ch, s; + sample_t* ptr; + float* channel; + + /* convert float PCM (multichannel float array, with pcm[0]=ch0, pcm[1]=ch1, pcm[2]=ch0, etc) + * to 16 bit signed PCM ints (host order) and interleave + fix clipping */ + for (ch = 0; ch < channels; ch++) { + /* channels should be in standard order unlike Ogg Vorbis (at least in FSB) */ + ptr = outbuf + ch; + channel = pcm[ch]; + for (s = 0; s < samples_to_do; s++) { + int val = (int)floor(channel[s] * 32767.0f + 0.5f); + if (val > 32767) val = 32767; + else if (val < -32768) val = -32768; + + *ptr = val; + ptr += channels; + } + } +} + +/* ********************************************** */ + +void free_vorbis_custom(vorbis_custom_codec_data* data) { + if (!data) + return; + + /* internal decoder cleanup */ + vorbis_block_clear(&data->vb); + vorbis_dsp_clear(&data->vd); + vorbis_comment_clear(&data->vc); + vorbis_info_clear(&data->vi); + + free(data->buffer); + free(data); +} + +void reset_vorbis_custom(VGMSTREAM* vgmstream) { + vorbis_custom_codec_data *data = vgmstream->codec_data; + if (!data) return; + + vorbis_synthesis_restart(&data->vd); + data->samples_to_discard = 0; +} + +void seek_vorbis_custom(VGMSTREAM* vgmstream, int32_t num_sample) { + vorbis_custom_codec_data *data = vgmstream->codec_data; + if (!data) return; + + /* Seeking is provided by the Ogg layer, so with custom vorbis we'd need seek tables instead. + * To avoid having to parse different formats we'll just discard until the expected sample */ + vorbis_synthesis_restart(&data->vd); + data->samples_to_discard = num_sample; + if (vgmstream->loop_ch) + vgmstream->loop_ch[0].offset = vgmstream->loop_ch[0].channel_start_offset; +} + +#endif diff --git a/src/coding/vorbis_custom_decoder.h b/src/coding/vorbis_custom_decoder.h index 4e51f391..8080800d 100644 --- a/src/coding/vorbis_custom_decoder.h +++ b/src/coding/vorbis_custom_decoder.h @@ -1,54 +1,54 @@ -#ifndef _VORBIS_CUSTOM_DECODER_H_ -#define _VORBIS_CUSTOM_DECODER_H_ - -#include "../vgmstream.h" -#include "../coding/coding.h" - -/* used by vorbis_custom_decoder.c, but scattered in other .c files */ -#ifdef VGM_USE_VORBIS - -/* custom Vorbis without Ogg layer */ -struct vorbis_custom_codec_data { - vorbis_info vi; /* stream settings */ - vorbis_comment vc; /* stream comments */ - vorbis_dsp_state vd; /* decoder global state */ - vorbis_block vb; /* decoder local state */ - ogg_packet op; /* fake packet for internal use */ - - uint8_t * buffer; /* internal raw data buffer */ - size_t buffer_size; - - size_t samples_to_discard; /* for looping purposes */ - int samples_full; /* flag, samples available in vorbis buffers */ - - vorbis_custom_t type; /* Vorbis subtype */ - vorbis_custom_config config; /* config depending on the mode */ - - /* Wwise Vorbis: saved data to reconstruct modified packets */ - uint8_t mode_blockflag[64+1]; /* max 6b+1; flags 'n stuff */ - int mode_bits; /* bits to store mode_number */ - uint8_t prev_blockflag; /* blockflag in the last decoded packet */ - /* Ogg-style Vorbis: packet within a page */ - int current_packet; - /* reference for page/blocks */ - off_t block_offset; - size_t block_size; - - int prev_block_samples; /* count for optimization */ -}; - - -int vorbis_custom_setup_init_fsb(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data); -int vorbis_custom_setup_init_wwise(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data); -int vorbis_custom_setup_init_ogl(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data); -int vorbis_custom_setup_init_sk(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data); -int vorbis_custom_setup_init_vid1(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data); - -int vorbis_custom_parse_packet_fsb(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data); -int vorbis_custom_parse_packet_wwise(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data); -int vorbis_custom_parse_packet_ogl(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data); -int vorbis_custom_parse_packet_sk(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data); -int vorbis_custom_parse_packet_vid1(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data); -#endif/* VGM_USE_VORBIS */ - -#endif/*_VORBIS_CUSTOM_DECODER_H_ */ +#ifndef _VORBIS_CUSTOM_DECODER_H_ +#define _VORBIS_CUSTOM_DECODER_H_ + +#include "../vgmstream.h" +#include "../coding/coding.h" + +/* used by vorbis_custom_decoder.c, but scattered in other .c files */ +#ifdef VGM_USE_VORBIS + +/* custom Vorbis without Ogg layer */ +struct vorbis_custom_codec_data { + vorbis_info vi; /* stream settings */ + vorbis_comment vc; /* stream comments */ + vorbis_dsp_state vd; /* decoder global state */ + vorbis_block vb; /* decoder local state */ + ogg_packet op; /* fake packet for internal use */ + + uint8_t* buffer; /* internal raw data buffer */ + size_t buffer_size; + + size_t samples_to_discard; /* for looping purposes */ + int samples_full; /* flag, samples available in vorbis buffers */ + + vorbis_custom_t type; /* Vorbis subtype */ + vorbis_custom_config config; /* config depending on the mode */ + + /* Wwise Vorbis: saved data to reconstruct modified packets */ + uint8_t mode_blockflag[64+1]; /* max 6b+1; flags 'n stuff */ + int mode_bits; /* bits to store mode_number */ + uint8_t prev_blockflag; /* blockflag in the last decoded packet */ + /* Ogg-style Vorbis: packet within a page */ + int current_packet; + /* reference for page/blocks */ + off_t block_offset; + size_t block_size; + + int prev_block_samples; /* count for optimization */ +}; + + +int vorbis_custom_setup_init_fsb(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data); +int vorbis_custom_setup_init_wwise(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data); +int vorbis_custom_setup_init_ogl(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data); +int vorbis_custom_setup_init_sk(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data); +int vorbis_custom_setup_init_vid1(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data); + +int vorbis_custom_parse_packet_fsb(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data); +int vorbis_custom_parse_packet_wwise(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data); +int vorbis_custom_parse_packet_ogl(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data); +int vorbis_custom_parse_packet_sk(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data); +int vorbis_custom_parse_packet_vid1(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data* data); +#endif/* VGM_USE_VORBIS */ + +#endif/*_VORBIS_CUSTOM_DECODER_H_ */ diff --git a/src/coding/vorbis_custom_utils_fsb.c b/src/coding/vorbis_custom_utils_fsb.c index 30928326..cb43c11f 100644 --- a/src/coding/vorbis_custom_utils_fsb.c +++ b/src/coding/vorbis_custom_utils_fsb.c @@ -1,269 +1,269 @@ -#include "vorbis_custom_decoder.h" - -#ifdef VGM_USE_VORBIS -#include - -#define FSB_VORBIS_USE_PRECOMPILED_FVS 1 /* if enabled vgmstream weights ~600kb more but doesn't need external .fvs packets */ -#if FSB_VORBIS_USE_PRECOMPILED_FVS -#include "vorbis_custom_data_fsb.h" -#endif - - -/* **************************************************************************** */ -/* DEFS */ -/* **************************************************************************** */ - -static int build_header_identification(uint8_t * buf, size_t bufsize, int channels, int sample_rate, int blocksize_short, int blocksize_long); -static int build_header_comment(uint8_t * buf, size_t bufsize); -static int build_header_setup(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile); - -static int load_fvs_file_single(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile); -static int load_fvs_file_multi(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile); -static int load_fvs_array(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile); - - -/* **************************************************************************** */ -/* EXTERNAL API */ -/* **************************************************************************** */ - -/** - * FSB references an external setup packet by the setup_id, and packets have mini headers with the size. - * - * Format info from python-fsb5 (https://github.com/HearthSim/python-fsb5) and - * fsb-vorbis-extractor (https://github.com/tmiasko/fsb-vorbis-extractor). - */ -int vorbis_custom_setup_init_fsb(STREAMFILE *streamFile, off_t start_offset, vorbis_custom_codec_data *data) { - vorbis_custom_config cfg = data->config; - - data->op.bytes = build_header_identification(data->buffer, data->buffer_size, cfg.channels, cfg.sample_rate, 256, 2048); /* FSB default block sizes */ - if (!data->op.bytes) goto fail; - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */ - - data->op.bytes = build_header_comment(data->buffer, data->buffer_size); - if (!data->op.bytes) goto fail; - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */ - - data->op.bytes = build_header_setup(data->buffer, data->buffer_size, cfg.setup_id, streamFile); - if (!data->op.bytes) goto fail; - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */ - - return 1; - -fail: - return 0; -} - - -int vorbis_custom_parse_packet_fsb(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data *data) { - size_t bytes; - - /* get next packet size from the FSB 16b header (doesn't count this 16b) */ - data->op.bytes = (uint16_t)read_16bitLE(stream->offset, stream->streamfile); - stream->offset += 2; - if (data->op.bytes == 0 || data->op.bytes == 0xFFFF || data->op.bytes > data->buffer_size) goto fail; /* EOF or end padding */ - - /* read raw block */ - bytes = read_streamfile(data->buffer,stream->offset, data->op.bytes,stream->streamfile); - stream->offset += data->op.bytes; - if (bytes != data->op.bytes) goto fail; /* wrong packet? */ - - return 1; - -fail: - return 0; -} - -/* **************************************************************************** */ -/* INTERNAL HELPERS */ -/* **************************************************************************** */ - -static int build_header_identification(uint8_t * buf, size_t bufsize, int channels, int sample_rate, int blocksize_short, int blocksize_long) { - int bytes = 0x1e; - uint8_t blocksizes, exp_blocksize_0, exp_blocksize_1; - - if (bytes > bufsize) return 0; - - /* guetto log2 for allowed blocksizes (2-exp), could be improved */ - switch(blocksize_long) { - case 64: exp_blocksize_0 = 6; break; - case 128: exp_blocksize_0 = 7; break; - case 256: exp_blocksize_0 = 8; break; - case 512: exp_blocksize_0 = 9; break; - case 1024: exp_blocksize_0 = 10; break; - case 2048: exp_blocksize_0 = 11; break; - case 4096: exp_blocksize_0 = 12; break; - case 8192: exp_blocksize_0 = 13; break; - default: return 0; - } - switch(blocksize_short) { - case 64: exp_blocksize_1 = 6; break; - case 128: exp_blocksize_1 = 7; break; - case 256: exp_blocksize_1 = 8; break; - case 512: exp_blocksize_1 = 9; break; - case 1024: exp_blocksize_1 = 10; break; - case 2048: exp_blocksize_1 = 11; break; - case 4096: exp_blocksize_1 = 12; break; - case 8192: exp_blocksize_1 = 13; break; - default: return 0; - } - blocksizes = (exp_blocksize_0 << 4) | (exp_blocksize_1); - - put_8bit (buf+0x00, 0x01); /* packet_type (id) */ - memcpy (buf+0x01, "vorbis", 6); /* id */ - put_32bitLE(buf+0x07, 0x00); /* vorbis_version (fixed) */ - put_8bit (buf+0x0b, channels); /* audio_channels */ - put_32bitLE(buf+0x0c, sample_rate); /* audio_sample_rate */ - put_32bitLE(buf+0x10, 0x00); /* bitrate_maximum (optional hint) */ - put_32bitLE(buf+0x14, 0x00); /* bitrate_nominal (optional hint) */ - put_32bitLE(buf+0x18, 0x00); /* bitrate_minimum (optional hint) */ - put_8bit (buf+0x1c, blocksizes); /* blocksize_0 + blocksize_1 nibbles */ - put_8bit (buf+0x1d, 0x01); /* framing_flag (fixed) */ - - return bytes; -} - -static int build_header_comment(uint8_t * buf, size_t bufsize) { - int bytes = 0x19; - - if (bytes > bufsize) return 0; - - put_8bit (buf+0x00, 0x03); /* packet_type (comments) */ - memcpy (buf+0x01, "vorbis", 6); /* id */ - put_32bitLE(buf+0x07, 0x09); /* vendor_length */ - memcpy (buf+0x0b, "vgmstream", 9); /* vendor_string */ - put_32bitLE(buf+0x14, 0x00); /* user_comment_list_length */ - put_8bit (buf+0x18, 0x01); /* framing_flag (fixed) */ - - return bytes; -} - -static int build_header_setup(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile) { - int bytes; - - /* try to locate from the precompiled list */ - bytes = load_fvs_array(buf, bufsize, setup_id, streamFile); - if (bytes) - return bytes; - - /* try to load from external files */ - bytes = load_fvs_file_single(buf, bufsize, setup_id, streamFile); - if (bytes) - return bytes; - - bytes = load_fvs_file_multi(buf, bufsize, setup_id, streamFile); - if (bytes) - return bytes; - - /* not found */ - VGM_LOG("FSB Vorbis: setup_id %08x not found\n", setup_id); - return 0; -} - -static int load_fvs_file_single(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile) { - STREAMFILE * streamFileSetup = NULL; - - { - char setupname[PATH_LIMIT]; - char pathname[PATH_LIMIT]; - char *path; - - /* read "(dir/).fvs_{setup_id}" */ - streamFile->get_name(streamFile,pathname,sizeof(pathname)); - path = strrchr(pathname,DIR_SEPARATOR); - if (path) - *(path+1) = '\0'; - else - pathname[0] = '\0'; - - snprintf(setupname,PATH_LIMIT,"%s.fvs_%08x", pathname, setup_id); - streamFileSetup = streamFile->open(streamFile,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE); - } - - if (streamFileSetup) { - /* file found, get contents into the buffer */ - size_t bytes = streamFileSetup->get_size(streamFileSetup); - if (bytes > bufsize) goto fail; - - if (read_streamfile(buf, 0, bytes, streamFileSetup) != bytes) - goto fail; - - streamFileSetup->close(streamFileSetup); - return bytes; - } - -fail: - if (streamFileSetup) streamFileSetup->close(streamFileSetup); - return 0; -} - -static int load_fvs_file_multi(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile) { - STREAMFILE * streamFileSetup = NULL; - - { - char setupname[PATH_LIMIT]; - char pathname[PATH_LIMIT]; - char *path; - - /* read "(dir/).fvs" */ - streamFile->get_name(streamFile,pathname,sizeof(pathname)); - path = strrchr(pathname,DIR_SEPARATOR); - if (path) - *(path+1) = '\0'; - else - pathname[0] = '\0'; - - snprintf(setupname,PATH_LIMIT,"%s.fvs", pathname); - streamFileSetup = streamFile->open(streamFile,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE); - } - - if (streamFileSetup) { - /* file found: read mini-header (format by bnnm, feel free to change) and locate FVS */ - int entries, i; - uint32_t offset = 0, size = 0; - - if (read_32bitBE(0x0, streamFileSetup) != 0x56465653) goto fail; /* "VFVS" */ - entries = read_32bitLE(0x08, streamFileSetup); /* 0x04=v0, 0x0c-0x20: reserved */ - if (entries <= 0) goto fail; - - for (i=0; i < entries; i++) { /* entry = id, offset, size, reserved */ - if ((uint32_t)read_32bitLE(0x20 + i*0x10, streamFileSetup) == setup_id) { - offset = read_32bitLE(0x24 + i*0x10, streamFileSetup); - size = read_32bitLE(0x28 + i*0x10, streamFileSetup); - break; - } - } - if (!size || !offset || size > bufsize) goto fail; - - /* read into buf */ - if (read_streamfile(buf, offset, size, streamFileSetup) != size) - goto fail; - - streamFileSetup->close(streamFileSetup); - return size; - } - -fail: - if (streamFileSetup) streamFileSetup->close(streamFileSetup); - return 0; -} - -static int load_fvs_array(uint8_t * buf, size_t bufsize, uint32_t setup_id, STREAMFILE *streamFile) { -#if FSB_VORBIS_USE_PRECOMPILED_FVS - int i, list_length; - - list_length = sizeof(fvs_list) / sizeof(fvs_info); - for (i=0; i < list_length; i++) { - if (fvs_list[i].id == setup_id) { - if (fvs_list[i].size > bufsize) goto fail; - /* found: copy data as-is */ - memcpy(buf,fvs_list[i].setup, fvs_list[i].size); - return fvs_list[i].size; - } - } - -fail: -#endif - return 0; -} - -#endif +#include "vorbis_custom_decoder.h" + +#ifdef VGM_USE_VORBIS +#include + +#define FSB_VORBIS_USE_PRECOMPILED_FVS 1 /* if enabled vgmstream weights ~600kb more but doesn't need external .fvs packets */ +#if FSB_VORBIS_USE_PRECOMPILED_FVS +#include "vorbis_custom_data_fsb.h" +#endif + + +/* **************************************************************************** */ +/* DEFS */ +/* **************************************************************************** */ + +static int build_header_identification(uint8_t* buf, size_t bufsize, int channels, int sample_rate, int blocksize_short, int blocksize_long); +static int build_header_comment(uint8_t* buf, size_t bufsize); +static int build_header_setup(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf); + +static int load_fvs_file_single(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf); +static int load_fvs_file_multi(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf); +static int load_fvs_array(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf); + + +/* **************************************************************************** */ +/* EXTERNAL API */ +/* **************************************************************************** */ + +/** + * FSB references an external setup packet by the setup_id, and packets have mini headers with the size. + * + * Format info from python-fsb5 (https://github.com/HearthSim/python-fsb5) and + * fsb-vorbis-extractor (https://github.com/tmiasko/fsb-vorbis-extractor). + */ +int vorbis_custom_setup_init_fsb(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data) { + vorbis_custom_config cfg = data->config; + + data->op.bytes = build_header_identification(data->buffer, data->buffer_size, cfg.channels, cfg.sample_rate, 256, 2048); /* FSB default block sizes */ + if (!data->op.bytes) goto fail; + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */ + + data->op.bytes = build_header_comment(data->buffer, data->buffer_size); + if (!data->op.bytes) goto fail; + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */ + + data->op.bytes = build_header_setup(data->buffer, data->buffer_size, cfg.setup_id, sf); + if (!data->op.bytes) goto fail; + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */ + + return 1; + +fail: + return 0; +} + + +int vorbis_custom_parse_packet_fsb(VGMSTREAMCHANNEL* stream, vorbis_custom_codec_data* data) { + size_t bytes; + + /* get next packet size from the FSB 16b header (doesn't count this 16b) */ + data->op.bytes = (uint16_t)read_16bitLE(stream->offset, stream->streamfile); + stream->offset += 2; + if (data->op.bytes == 0 || data->op.bytes == 0xFFFF || data->op.bytes > data->buffer_size) goto fail; /* EOF or end padding */ + + /* read raw block */ + bytes = read_streamfile(data->buffer,stream->offset, data->op.bytes,stream->streamfile); + stream->offset += data->op.bytes; + if (bytes != data->op.bytes) goto fail; /* wrong packet? */ + + return 1; + +fail: + return 0; +} + +/* **************************************************************************** */ +/* INTERNAL HELPERS */ +/* **************************************************************************** */ + +static int build_header_identification(uint8_t* buf, size_t bufsize, int channels, int sample_rate, int blocksize_short, int blocksize_long) { + int bytes = 0x1e; + uint8_t blocksizes, exp_blocksize_0, exp_blocksize_1; + + if (bytes > bufsize) return 0; + + /* guetto log2 for allowed blocksizes (2-exp), could be improved */ + switch(blocksize_long) { + case 64: exp_blocksize_0 = 6; break; + case 128: exp_blocksize_0 = 7; break; + case 256: exp_blocksize_0 = 8; break; + case 512: exp_blocksize_0 = 9; break; + case 1024: exp_blocksize_0 = 10; break; + case 2048: exp_blocksize_0 = 11; break; + case 4096: exp_blocksize_0 = 12; break; + case 8192: exp_blocksize_0 = 13; break; + default: return 0; + } + switch(blocksize_short) { + case 64: exp_blocksize_1 = 6; break; + case 128: exp_blocksize_1 = 7; break; + case 256: exp_blocksize_1 = 8; break; + case 512: exp_blocksize_1 = 9; break; + case 1024: exp_blocksize_1 = 10; break; + case 2048: exp_blocksize_1 = 11; break; + case 4096: exp_blocksize_1 = 12; break; + case 8192: exp_blocksize_1 = 13; break; + default: return 0; + } + blocksizes = (exp_blocksize_0 << 4) | (exp_blocksize_1); + + put_8bit (buf+0x00, 0x01); /* packet_type (id) */ + memcpy (buf+0x01, "vorbis", 6); /* id */ + put_32bitLE(buf+0x07, 0x00); /* vorbis_version (fixed) */ + put_8bit (buf+0x0b, channels); /* audio_channels */ + put_32bitLE(buf+0x0c, sample_rate); /* audio_sample_rate */ + put_32bitLE(buf+0x10, 0x00); /* bitrate_maximum (optional hint) */ + put_32bitLE(buf+0x14, 0x00); /* bitrate_nominal (optional hint) */ + put_32bitLE(buf+0x18, 0x00); /* bitrate_minimum (optional hint) */ + put_8bit (buf+0x1c, blocksizes); /* blocksize_0 + blocksize_1 nibbles */ + put_8bit (buf+0x1d, 0x01); /* framing_flag (fixed) */ + + return bytes; +} + +static int build_header_comment(uint8_t* buf, size_t bufsize) { + int bytes = 0x19; + + if (bytes > bufsize) return 0; + + put_8bit (buf+0x00, 0x03); /* packet_type (comments) */ + memcpy (buf+0x01, "vorbis", 6); /* id */ + put_32bitLE(buf+0x07, 0x09); /* vendor_length */ + memcpy (buf+0x0b, "vgmstream", 9); /* vendor_string */ + put_32bitLE(buf+0x14, 0x00); /* user_comment_list_length */ + put_8bit (buf+0x18, 0x01); /* framing_flag (fixed) */ + + return bytes; +} + +static int build_header_setup(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) { + int bytes; + + /* try to locate from the precompiled list */ + bytes = load_fvs_array(buf, bufsize, setup_id, sf); + if (bytes) + return bytes; + + /* try to load from external files */ + bytes = load_fvs_file_single(buf, bufsize, setup_id, sf); + if (bytes) + return bytes; + + bytes = load_fvs_file_multi(buf, bufsize, setup_id, sf); + if (bytes) + return bytes; + + /* not found */ + VGM_LOG("FSB Vorbis: setup_id %08x not found\n", setup_id); + return 0; +} + +static int load_fvs_file_single(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) { + STREAMFILE* sf_setup = NULL; + + { + char setupname[PATH_LIMIT]; + char pathname[PATH_LIMIT]; + char *path; + + /* read "(dir/).fvs_{setup_id}" */ + sf->get_name(sf,pathname,sizeof(pathname)); + path = strrchr(pathname,DIR_SEPARATOR); + if (path) + *(path+1) = '\0'; + else + pathname[0] = '\0'; + + snprintf(setupname,PATH_LIMIT,"%s.fvs_%08x", pathname, setup_id); + sf_setup = sf->open(sf,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE); + } + + if (sf_setup) { + /* file found, get contents into the buffer */ + size_t bytes = sf_setup->get_size(sf_setup); + if (bytes > bufsize) goto fail; + + if (read_streamfile(buf, 0, bytes, sf_setup) != bytes) + goto fail; + + sf_setup->close(sf_setup); + return bytes; + } + +fail: + if (sf_setup) sf_setup->close(sf_setup); + return 0; +} + +static int load_fvs_file_multi(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) { + STREAMFILE* sf_setup = NULL; + + { + char setupname[PATH_LIMIT]; + char pathname[PATH_LIMIT]; + char* path; + + /* read "(dir/).fvs" */ + sf->get_name(sf,pathname,sizeof(pathname)); + path = strrchr(pathname,DIR_SEPARATOR); + if (path) + *(path+1) = '\0'; + else + pathname[0] = '\0'; + + snprintf(setupname,PATH_LIMIT,"%s.fvs", pathname); + sf_setup = sf->open(sf,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE); + } + + if (sf_setup) { + /* file found: read mini-header (format by bnnm, feel free to change) and locate FVS */ + int entries, i; + uint32_t offset = 0, size = 0; + + if (read_32bitBE(0x0, sf_setup) != 0x56465653) goto fail; /* "VFVS" */ + entries = read_32bitLE(0x08, sf_setup); /* 0x04=v0, 0x0c-0x20: reserved */ + if (entries <= 0) goto fail; + + for (i=0; i < entries; i++) { /* entry = id, offset, size, reserved */ + if ((uint32_t)read_32bitLE(0x20 + i*0x10, sf_setup) == setup_id) { + offset = read_32bitLE(0x24 + i*0x10, sf_setup); + size = read_32bitLE(0x28 + i*0x10, sf_setup); + break; + } + } + if (!size || !offset || size > bufsize) goto fail; + + /* read into buf */ + if (read_streamfile(buf, offset, size, sf_setup) != size) + goto fail; + + sf_setup->close(sf_setup); + return size; + } + +fail: + if (sf_setup) sf_setup->close(sf_setup); + return 0; +} + +static int load_fvs_array(uint8_t* buf, size_t bufsize, uint32_t setup_id, STREAMFILE* sf) { +#if FSB_VORBIS_USE_PRECOMPILED_FVS + int i, list_length; + + list_length = sizeof(fvs_list) / sizeof(fvs_info); + for (i=0; i < list_length; i++) { + if (fvs_list[i].id == setup_id) { + if (fvs_list[i].size > bufsize) goto fail; + /* found: copy data as-is */ + memcpy(buf,fvs_list[i].setup, fvs_list[i].size); + return fvs_list[i].size; + } + } + +fail: +#endif + return 0; +} + +#endif diff --git a/src/coding/vorbis_custom_utils_ogl.c b/src/coding/vorbis_custom_utils_ogl.c index 8e746362..bafa949c 100644 --- a/src/coding/vorbis_custom_utils_ogl.c +++ b/src/coding/vorbis_custom_utils_ogl.c @@ -1,71 +1,71 @@ -#include "vorbis_custom_decoder.h" - -#ifdef VGM_USE_VORBIS -#include - - -/* **************************************************************************** */ -/* EXTERNAL API */ -/* **************************************************************************** */ - -/** - * OGL removes the Ogg layer and uses 16b packet headers, that have the size of the next packet, but - * the lower 2b need to be removed (usually 00 but 01 for the id packet, not sure about the meaning). - */ -int vorbis_custom_setup_init_ogl(STREAMFILE *streamFile, off_t start_offset, vorbis_custom_codec_data *data) { - off_t offset = start_offset; - size_t packet_size; - - /* read 3 packets with triad (id/comment/setup), each with an OGL header */ - - /* normal identificacion packet */ - packet_size = (uint16_t)read_16bitLE(offset, streamFile) >> 2; - if (packet_size > data->buffer_size) goto fail; - data->op.bytes = read_streamfile(data->buffer,offset+2,packet_size, streamFile); - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */ - offset += 2+packet_size; - - /* normal comment packet */ - packet_size = (uint16_t)read_16bitLE(offset, streamFile) >> 2; - if (packet_size > data->buffer_size) goto fail; - data->op.bytes = read_streamfile(data->buffer,offset+2,packet_size, streamFile); - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */ - offset += 2+packet_size; - - /* normal setup packet */ - packet_size = (uint16_t)read_16bitLE(offset, streamFile) >> 2; - if (packet_size > data->buffer_size) goto fail; - data->op.bytes = read_streamfile(data->buffer,offset+2,packet_size, streamFile); - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */ - offset += 2+packet_size; - - /* data starts after triad */ - data->config.data_start_offset = offset; - - return 1; - -fail: - return 0; -} - - -int vorbis_custom_parse_packet_ogl(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data *data) { - size_t bytes; - - /* get next packet size from the OGL 16b header (upper 14b) */ - data->op.bytes = (uint16_t)read_16bitLE(stream->offset, stream->streamfile) >> 2; - stream->offset += 2; - if (data->op.bytes == 0 || data->op.bytes == 0xFFFF || data->op.bytes > data->buffer_size) goto fail; /* EOF or end padding */ - - /* read raw block */ - bytes = read_streamfile(data->buffer,stream->offset, data->op.bytes,stream->streamfile); - stream->offset += data->op.bytes; - if (bytes != data->op.bytes) goto fail; /* wrong packet? */ - - return 1; - -fail: - return 0; -} - -#endif +#include "vorbis_custom_decoder.h" + +#ifdef VGM_USE_VORBIS +#include + + +/* **************************************************************************** */ +/* EXTERNAL API */ +/* **************************************************************************** */ + +/** + * OGL removes the Ogg layer and uses 16b packet headers, that have the size of the next packet, but + * the lower 2b need to be removed (usually 00 but 01 for the id packet, not sure about the meaning). + */ +int vorbis_custom_setup_init_ogl(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data) { + off_t offset = start_offset; + size_t packet_size; + + /* read 3 packets with triad (id/comment/setup), each with an OGL header */ + + /* normal identificacion packet */ + packet_size = (uint16_t)read_16bitLE(offset, sf) >> 2; + if (packet_size > data->buffer_size) goto fail; + data->op.bytes = read_streamfile(data->buffer,offset+2,packet_size, sf); + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */ + offset += 2+packet_size; + + /* normal comment packet */ + packet_size = (uint16_t)read_16bitLE(offset, sf) >> 2; + if (packet_size > data->buffer_size) goto fail; + data->op.bytes = read_streamfile(data->buffer,offset+2,packet_size, sf); + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */ + offset += 2+packet_size; + + /* normal setup packet */ + packet_size = (uint16_t)read_16bitLE(offset, sf) >> 2; + if (packet_size > data->buffer_size) goto fail; + data->op.bytes = read_streamfile(data->buffer,offset+2,packet_size, sf); + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */ + offset += 2+packet_size; + + /* data starts after triad */ + data->config.data_start_offset = offset; + + return 1; + +fail: + return 0; +} + + +int vorbis_custom_parse_packet_ogl(VGMSTREAMCHANNEL* stream, vorbis_custom_codec_data* data) { + size_t bytes; + + /* get next packet size from the OGL 16b header (upper 14b) */ + data->op.bytes = (uint16_t)read_16bitLE(stream->offset, stream->streamfile) >> 2; + stream->offset += 2; + if (data->op.bytes == 0 || data->op.bytes == 0xFFFF || data->op.bytes > data->buffer_size) goto fail; /* EOF or end padding */ + + /* read raw block */ + bytes = read_streamfile(data->buffer,stream->offset, data->op.bytes,stream->streamfile); + stream->offset += data->op.bytes; + if (bytes != data->op.bytes) goto fail; /* wrong packet? */ + + return 1; + +fail: + return 0; +} + +#endif diff --git a/src/coding/vorbis_custom_utils_sk.c b/src/coding/vorbis_custom_utils_sk.c index 408a7e20..9688eea8 100644 --- a/src/coding/vorbis_custom_utils_sk.c +++ b/src/coding/vorbis_custom_utils_sk.c @@ -1,187 +1,187 @@ -#include "vorbis_custom_decoder.h" - -#ifdef VGM_USE_VORBIS -#include - -/* **************************************************************************** */ -/* DEFS */ -/* **************************************************************************** */ - -static int get_page_info(STREAMFILE *streamFile, off_t page_offset, off_t *out_packet_offset, size_t *out_packet_size, int *out_page_packets, int target_packet); -static int build_header(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile, off_t packet_offset, size_t packet_size); - - -/* **************************************************************************** */ -/* EXTERNAL API */ -/* **************************************************************************** */ - -/** - * SK just replaces the id 0x4F676753 ("OggS") by 0x11534B10 (\11"SK"\10), and the word "vorbis" by "SK" - * in init packets (for obfuscation, surely). So essentially we are parsing regular Ogg here. - * - * A simpler way to implement this would be in ogg_vorbis_file with read callbacks (pretend this is proof of concept). - */ -int vorbis_custom_setup_init_sk(STREAMFILE *streamFile, off_t start_offset, vorbis_custom_codec_data *data) { - off_t offset = start_offset; - off_t id_offset = 0, comment_offset = 0, setup_offset = 0; - size_t id_size = 0, comment_size = 0, setup_size = 0; - int page_packets; - - /* rebuild header packets, they are standard except the "vorbis" keyword is replaced by "SK" */ - - /* first page has the id packet */ - if (!get_page_info(streamFile, offset, &id_offset, &id_size, &page_packets, 0)) goto fail; - if (page_packets != 1) goto fail; - offset = id_offset + id_size; - - /* second page has the comment and setup packets */ - if (!get_page_info(streamFile, offset, &comment_offset, &comment_size, &page_packets, 0)) goto fail; - if (page_packets != 2) goto fail; - if (!get_page_info(streamFile, offset, &setup_offset, &setup_size, &page_packets, 1)) goto fail; - if (page_packets != 2) goto fail; - offset = comment_offset + comment_size + setup_size; - - - /* init with all offsets found */ - data->op.bytes = build_header(data->buffer, data->buffer_size, streamFile, id_offset, id_size); - if (!data->op.bytes) goto fail; - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */ - - data->op.bytes = build_header(data->buffer, data->buffer_size, streamFile, comment_offset, comment_size); - if (!data->op.bytes) goto fail; - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */ - - data->op.bytes = build_header(data->buffer, data->buffer_size, streamFile, setup_offset, setup_size); - if (!data->op.bytes) goto fail; - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */ - - /* data starts after triad */ - data->config.data_start_offset = offset; - - return 1; - -fail: - return 0; -} - - -int vorbis_custom_parse_packet_sk(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data *data) { - off_t packet_offset = 0; - size_t packet_size = 0; - int page_packets; - int res; - - /* read OggS/SK page and get current packet */ - res = get_page_info(stream->streamfile, stream->offset, &packet_offset, &packet_size, &page_packets, data->current_packet); - data->current_packet++; - if (!res || packet_size > data->buffer_size) goto fail; - - /* read raw block */ - data->op.bytes = read_streamfile(data->buffer, packet_offset, packet_size, stream->streamfile); - if (data->op.bytes != packet_size) goto fail; /* wrong packet? */ - - /* go next page when processed all packets in page */ - if (data->current_packet >= page_packets) { - if (!get_page_info(stream->streamfile, stream->offset, &packet_offset, &packet_size, &page_packets, -1)) goto fail; - stream->offset = packet_offset + packet_size; - data->current_packet = 0; - } - - return 1; - -fail: - return 0; -} - -/* **************************************************************************** */ -/* INTERNAL HELPERS */ -/* **************************************************************************** */ - -/** - * Get packet info from an Ogg page, from segment/packet N (-1 = all segments) - * - * Page format: - * 0x00(4): capture pattern ("OggS") - * 0x01(1): stream structure version - * 0x05(1): header type flag - * 0x06(8): absolute granule position - * 0x0e(4): stream serial number - * 0x12(4): page sequence number - * 0x16(4): page checksum - * 0x1a(1): page segments (total bytes in segment table) - * 0x1b(n): segment table (N bytes, 1 packet is sum of sizes until != 0xFF) - * 0x--(n): data - * Reference: https://xiph.org/ogg/doc/framing.html - */ -static int get_page_info(STREAMFILE *streamFile, off_t page_offset, off_t *out_packet_offset, size_t *out_packet_size, int *out_page_packets, int target_packet) { - off_t table_offset, current_packet_offset, target_packet_offset = 0; - size_t total_packets_size = 0, current_packet_size = 0, target_packet_size = 0; - int page_packets = 0; - uint8_t segments; - int i; - - - if (read_32bitBE(page_offset+0x00, streamFile) != 0x11534B10) /* \11"SK"\10 */ - goto fail; /* not a valid page */ - /* No point on validating other stuff, but they look legal enough (CRC too it seems) */ - - segments = (uint8_t)read_8bit(page_offset+0x1a, streamFile); - - table_offset = page_offset + 0x1b; - current_packet_offset = page_offset + 0x1b + segments; /* first packet starts after segments */ - - /* process segments */ - for (i = 0; i < segments; i++) { - uint8_t segment_size = (uint8_t)read_8bit(table_offset, streamFile); - total_packets_size += segment_size; - current_packet_size += segment_size; - table_offset += 0x01; - - if (segment_size != 0xFF) { /* packet complete */ - page_packets++; - - if (target_packet+1 == page_packets) { - target_packet_offset = current_packet_offset; - target_packet_size = current_packet_size; - } - - /* keep reading to fill page_packets */ - current_packet_offset += current_packet_size; /* move to next packet */ - current_packet_size = 0; - } - } - - /* < 0 is accepted and returns first offset and all packets sizes */ - if (target_packet+1 > page_packets) goto fail; - if (target_packet < 0) { - target_packet_offset = page_offset + 0x1b + segments; /* first */ - target_packet_size = total_packets_size; - } - - if (out_packet_offset) *out_packet_offset = target_packet_offset; - if (out_packet_size) *out_packet_size = target_packet_size; - if (out_page_packets) *out_page_packets = page_packets; - - return 1; - -fail: - //VGM_LOG("SK Vorbis: failed to read page @ 0x%08lx\n", page_offset); - return 0; -} - -/* rebuild a "SK" header packet to a "vorbis" one */ -static int build_header(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile, off_t packet_offset, size_t packet_size) { - int bytes; - - if (0x07+packet_size-0x03 > bufsize) return 0; - - put_8bit (buf+0x00, read_8bit(packet_offset,streamFile)); /* packet_type */ - memcpy (buf+0x01, "vorbis", 6); /* id */ - bytes = read_streamfile(buf+0x07,packet_offset+0x03, packet_size-0x03,streamFile); /* copy rest (all except id+"SK") */ - if (packet_size-0x03 != bytes) - return 0; - - return 0x07+packet_size-0x03; -} - -#endif +#include "vorbis_custom_decoder.h" + +#ifdef VGM_USE_VORBIS +#include + +/* **************************************************************************** */ +/* DEFS */ +/* **************************************************************************** */ + +static int get_page_info(STREAMFILE* sf, off_t page_offset, off_t* p_packet_offset, size_t* p_packet_size, int* p_page_packets, int target_packet); +static int build_header(uint8_t* buf, size_t bufsize, STREAMFILE* sf, off_t packet_offset, size_t packet_size); + + +/* **************************************************************************** */ +/* EXTERNAL API */ +/* **************************************************************************** */ + +/** + * SK just replaces the id 0x4F676753 ("OggS") by 0x11534B10 (\11"SK"\10), and the word "vorbis" by "SK" + * in init packets (for obfuscation, surely). So essentially we are parsing regular Ogg here. + * + * A simpler way to implement this would be in ogg_vorbis_file with read callbacks (pretend this is proof of concept). + */ +int vorbis_custom_setup_init_sk(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data) { + off_t offset = start_offset; + off_t id_offset = 0, comment_offset = 0, setup_offset = 0; + size_t id_size = 0, comment_size = 0, setup_size = 0; + int page_packets; + + /* rebuild header packets, they are standard except the "vorbis" keyword is replaced by "SK" */ + + /* first page has the id packet */ + if (!get_page_info(sf, offset, &id_offset, &id_size, &page_packets, 0)) goto fail; + if (page_packets != 1) goto fail; + offset = id_offset + id_size; + + /* second page has the comment and setup packets */ + if (!get_page_info(sf, offset, &comment_offset, &comment_size, &page_packets, 0)) goto fail; + if (page_packets != 2) goto fail; + if (!get_page_info(sf, offset, &setup_offset, &setup_size, &page_packets, 1)) goto fail; + if (page_packets != 2) goto fail; + offset = comment_offset + comment_size + setup_size; + + + /* init with all offsets found */ + data->op.bytes = build_header(data->buffer, data->buffer_size, sf, id_offset, id_size); + if (!data->op.bytes) goto fail; + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */ + + data->op.bytes = build_header(data->buffer, data->buffer_size, sf, comment_offset, comment_size); + if (!data->op.bytes) goto fail; + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */ + + data->op.bytes = build_header(data->buffer, data->buffer_size, sf, setup_offset, setup_size); + if (!data->op.bytes) goto fail; + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */ + + /* data starts after triad */ + data->config.data_start_offset = offset; + + return 1; + +fail: + return 0; +} + + +int vorbis_custom_parse_packet_sk(VGMSTREAMCHANNEL* stream, vorbis_custom_codec_data* data) { + off_t packet_offset = 0; + size_t packet_size = 0; + int page_packets; + int res; + + /* read OggS/SK page and get current packet */ + res = get_page_info(stream->streamfile, stream->offset, &packet_offset, &packet_size, &page_packets, data->current_packet); + data->current_packet++; + if (!res || packet_size > data->buffer_size) goto fail; + + /* read raw block */ + data->op.bytes = read_streamfile(data->buffer, packet_offset, packet_size, stream->streamfile); + if (data->op.bytes != packet_size) goto fail; /* wrong packet? */ + + /* go next page when processed all packets in page */ + if (data->current_packet >= page_packets) { + if (!get_page_info(stream->streamfile, stream->offset, &packet_offset, &packet_size, &page_packets, -1)) goto fail; + stream->offset = packet_offset + packet_size; + data->current_packet = 0; + } + + return 1; + +fail: + return 0; +} + +/* **************************************************************************** */ +/* INTERNAL HELPERS */ +/* **************************************************************************** */ + +/** + * Get packet info from an Ogg page, from segment/packet N (-1 = all segments) + * + * Page format: + * 0x00(4): capture pattern ("OggS") + * 0x01(1): stream structure version + * 0x05(1): header type flag + * 0x06(8): absolute granule position + * 0x0e(4): stream serial number + * 0x12(4): page sequence number + * 0x16(4): page checksum + * 0x1a(1): page segments (total bytes in segment table) + * 0x1b(n): segment table (N bytes, 1 packet is sum of sizes until != 0xFF) + * 0x--(n): data + * Reference: https://xiph.org/ogg/doc/framing.html + */ +static int get_page_info(STREAMFILE* sf, off_t page_offset, off_t* p_packet_offset, size_t* p_packet_size, int* p_page_packets, int target_packet) { + off_t table_offset, current_packet_offset, target_packet_offset = 0; + size_t total_packets_size = 0, current_packet_size = 0, target_packet_size = 0; + int page_packets = 0; + uint8_t segments; + int i; + + + if (read_32bitBE(page_offset+0x00, sf) != 0x11534B10) /* \11"SK"\10 */ + goto fail; /* not a valid page */ + /* No point on validating other stuff, but they look legal enough (CRC too it seems) */ + + segments = (uint8_t)read_8bit(page_offset+0x1a, sf); + + table_offset = page_offset + 0x1b; + current_packet_offset = page_offset + 0x1b + segments; /* first packet starts after segments */ + + /* process segments */ + for (i = 0; i < segments; i++) { + uint8_t segment_size = (uint8_t)read_8bit(table_offset, sf); + total_packets_size += segment_size; + current_packet_size += segment_size; + table_offset += 0x01; + + if (segment_size != 0xFF) { /* packet complete */ + page_packets++; + + if (target_packet+1 == page_packets) { + target_packet_offset = current_packet_offset; + target_packet_size = current_packet_size; + } + + /* keep reading to fill page_packets */ + current_packet_offset += current_packet_size; /* move to next packet */ + current_packet_size = 0; + } + } + + /* < 0 is accepted and returns first offset and all packets sizes */ + if (target_packet+1 > page_packets) goto fail; + if (target_packet < 0) { + target_packet_offset = page_offset + 0x1b + segments; /* first */ + target_packet_size = total_packets_size; + } + + if (p_packet_offset) *p_packet_offset = target_packet_offset; + if (p_packet_size) *p_packet_size = target_packet_size; + if (p_page_packets) *p_page_packets = page_packets; + + return 1; + +fail: + //VGM_LOG("SK Vorbis: failed to read page @ 0x%08lx\n", page_offset); + return 0; +} + +/* rebuild a "SK" header packet to a "vorbis" one */ +static int build_header(uint8_t* buf, size_t bufsize, STREAMFILE* sf, off_t packet_offset, size_t packet_size) { + int bytes; + + if (0x07+packet_size-0x03 > bufsize) return 0; + + put_8bit (buf+0x00, read_8bit(packet_offset,sf)); /* packet_type */ + memcpy (buf+0x01, "vorbis", 6); /* id */ + bytes = read_streamfile(buf+0x07,packet_offset+0x03, packet_size-0x03,sf); /* copy rest (all except id+"SK") */ + if (packet_size-0x03 != bytes) + return 0; + + return 0x07+packet_size-0x03; +} + +#endif diff --git a/src/coding/vorbis_custom_utils_vid1.c b/src/coding/vorbis_custom_utils_vid1.c index ec869db3..e93d9e60 100644 --- a/src/coding/vorbis_custom_utils_vid1.c +++ b/src/coding/vorbis_custom_utils_vid1.c @@ -1,5 +1,8 @@ #include "vorbis_custom_decoder.h" +#define BITSTREAM_READ_ONLY /* config */ +#include "vorbis_bitreader.h" + #ifdef VGM_USE_VORBIS #include @@ -8,8 +11,8 @@ /* DEFS */ /* **************************************************************************** */ -static int get_packet_header(STREAMFILE *streamFile, off_t *offset, size_t *size); -static int build_header_comment(uint8_t * buf, size_t bufsize); +static int get_packet_header(STREAMFILE* sf, off_t* offset, size_t* size); +static int build_header_comment(uint8_t* buf, size_t bufsize); /* **************************************************************************** */ @@ -21,16 +24,16 @@ static int build_header_comment(uint8_t * buf, size_t bufsize); * * Info from hcs's vid1_2ogg: https://github.com/hcs64/vgm_ripping/tree/master/demux/vid1_2ogg */ -int vorbis_custom_setup_init_vid1(STREAMFILE *streamFile, off_t start_offset, vorbis_custom_codec_data *data) { +int vorbis_custom_setup_init_vid1(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data) { off_t offset = start_offset; size_t packet_size = 0; /* read header packets (id/setup), each with an VID1 header */ /* normal identificacion packet */ - get_packet_header(streamFile, &offset, &packet_size); + get_packet_header(sf, &offset, &packet_size); if (packet_size > data->buffer_size) goto fail; - data->op.bytes = read_streamfile(data->buffer,offset,packet_size, streamFile); + data->op.bytes = read_streamfile(data->buffer,offset,packet_size, sf); if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */ offset += packet_size; @@ -40,9 +43,9 @@ int vorbis_custom_setup_init_vid1(STREAMFILE *streamFile, off_t start_offset, vo if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */ /* normal setup packet */ - get_packet_header(streamFile, &offset, &packet_size); + get_packet_header(sf, &offset, &packet_size); if (packet_size > data->buffer_size) goto fail; - data->op.bytes = read_streamfile(data->buffer,offset,packet_size, streamFile); + data->op.bytes = read_streamfile(data->buffer,offset,packet_size, sf); if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */ offset += packet_size; @@ -53,7 +56,7 @@ fail: } -int vorbis_custom_parse_packet_vid1(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data *data) { +int vorbis_custom_parse_packet_vid1(VGMSTREAMCHANNEL* stream, vorbis_custom_codec_data* data) { size_t bytes; @@ -100,7 +103,7 @@ fail: /* INTERNAL HELPERS */ /* **************************************************************************** */ -static int build_header_comment(uint8_t * buf, size_t bufsize) { +static int build_header_comment(uint8_t* buf, size_t bufsize) { int bytes = 0x19; if (bytes > bufsize) return 0; @@ -116,26 +119,24 @@ static int build_header_comment(uint8_t * buf, size_t bufsize) { } /* read header in Vorbis bitpacking format */ -static int get_packet_header(STREAMFILE *streamFile, off_t *offset, size_t *size) { +static int get_packet_header(STREAMFILE* sf, off_t* offset, size_t* size) { uint8_t ibuf[0x04]; /* header buffer */ size_t ibufsize = 0x04; /* header ~max */ - vgm_bitstream ib = {0}; + bitstream_t ib = {0}; uint32_t size_bits; - if (read_streamfile(ibuf,(*offset),ibufsize, streamFile) != ibufsize) + if (read_streamfile(ibuf,(*offset),ibufsize, sf) != ibufsize) goto fail; - ib.buf = ibuf; - ib.bufsize = ibufsize; - ib.b_off = 0; - ib.mode = BITSTREAM_VORBIS; + + init_bitstream(&ib, ibuf, ibufsize); /* read using Vorbis weird LSF */ - r_bits(&ib, 4,&size_bits); - r_bits(&ib, (size_bits+1),(uint32_t*)size); + rv_bits(&ib, 4,&size_bits); + rv_bits(&ib, (size_bits+1),(uint32_t*)size); /* special meaning, seen in silent frames */ - if (size_bits == 0 && *size == 0 && (uint8_t)read_8bit(*offset, streamFile)==0x80) { + if (size_bits == 0 && *size == 0 && (uint8_t)read_8bit(*offset, sf) == 0x80) { *size = 0x01; } diff --git a/src/coding/vorbis_custom_utils_wwise.c b/src/coding/vorbis_custom_utils_wwise.c index eff24352..827ed348 100644 --- a/src/coding/vorbis_custom_utils_wwise.c +++ b/src/coding/vorbis_custom_utils_wwise.c @@ -1,1222 +1,1260 @@ -#include "vorbis_custom_decoder.h" - -#ifdef VGM_USE_VORBIS -#include - -#define WWISE_VORBIS_USE_PRECOMPILED_WVC 1 /* if enabled vgmstream weights ~150kb more but doesn't need external .wvc packets */ -#if WWISE_VORBIS_USE_PRECOMPILED_WVC -#include "vorbis_custom_data_wwise.h" -#endif - - -/* **************************************************************************** */ -/* DEFS */ -/* **************************************************************************** */ - -static size_t build_header_identification(uint8_t * buf, size_t bufsize, int channels, int sample_rate, int blocksize_short, int blocksize_long); -static size_t build_header_comment(uint8_t * buf, size_t bufsize); -static size_t get_packet_header(STREAMFILE *streamFile, off_t offset, wwise_header_t header_type, int * granulepos, size_t * packet_size, int big_endian); -static size_t rebuild_packet(uint8_t * obuf, size_t obufsize, STREAMFILE *streamFile, off_t offset, vorbis_custom_codec_data * data, int big_endian); -static size_t rebuild_setup(uint8_t * obuf, size_t obufsize, STREAMFILE *streamFile, off_t offset, vorbis_custom_codec_data * data, int big_endian, int channels); - -static int ww2ogg_generate_vorbis_packet(vgm_bitstream * ow, vgm_bitstream * iw, STREAMFILE *streamFile, off_t offset, vorbis_custom_codec_data * data, int big_endian); -static int ww2ogg_generate_vorbis_setup(vgm_bitstream * ow, vgm_bitstream * iw, vorbis_custom_codec_data * data, int channels, size_t packet_size, STREAMFILE *streamFile); -static int ww2ogg_codebook_library_copy(vgm_bitstream * ow, vgm_bitstream * iw); -static int ww2ogg_codebook_library_rebuild(vgm_bitstream * ow, vgm_bitstream * iw, size_t cb_size, STREAMFILE *streamFile); -static int ww2ogg_codebook_library_rebuild_by_id(vgm_bitstream * ow, uint32_t codebook_id, wwise_setup_t setup_type, STREAMFILE *streamFile); -static int ww2ogg_tremor_ilog(unsigned int v); -static unsigned int ww2ogg_tremor_book_maptype1_quantvals(unsigned int entries, unsigned int dimensions); - -static int load_wvc(uint8_t * ibuf, size_t ibufsize, uint32_t codebook_id, wwise_setup_t setup_type, STREAMFILE *streamFile); -static int load_wvc_file(uint8_t * buf, size_t bufsize, uint32_t codebook_id, STREAMFILE *streamFile); -static int load_wvc_array(uint8_t * buf, size_t bufsize, uint32_t codebook_id, wwise_setup_t setup_type); - - -/* **************************************************************************** */ -/* EXTERNAL API */ -/* **************************************************************************** */ - -/** - * Wwise stores a reduced setup, and packets have mini headers with the size, and data packets - * may reduced as well. The format evolved over time so there are many variations. - * The Wwise implementation uses Tremor (fixed-point Vorbis) but shouldn't matter. - * - * Format reverse-engineered by hcs in ww2ogg (https://github.com/hcs64/ww2ogg). - */ -int vorbis_custom_setup_init_wwise(STREAMFILE *streamFile, off_t start_offset, vorbis_custom_codec_data *data) { - size_t header_size, packet_size; - vorbis_custom_config cfg = data->config; - - if (cfg.setup_type == WWV_HEADER_TRIAD) { - /* read 3 Wwise packets with triad (id/comment/setup), each with a Wwise header */ - off_t offset = start_offset; - - /* normal identificacion packet */ - header_size = get_packet_header(streamFile, offset, cfg.header_type, (int*)&data->op.granulepos, &packet_size, cfg.big_endian); - if (!header_size || packet_size > data->buffer_size) goto fail; - data->op.bytes = read_streamfile(data->buffer,offset+header_size,packet_size, streamFile); - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */ - offset += header_size + packet_size; - - /* normal comment packet */ - header_size = get_packet_header(streamFile, offset, cfg.header_type, (int*)&data->op.granulepos, &packet_size, cfg.big_endian); - if (!header_size || packet_size > data->buffer_size) goto fail; - data->op.bytes = read_streamfile(data->buffer,offset+header_size,packet_size, streamFile); - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */ - offset += header_size + packet_size; - - /* normal setup packet */ - header_size = get_packet_header(streamFile, offset, cfg.header_type, (int*)&data->op.granulepos, &packet_size, cfg.big_endian); - if (!header_size || packet_size > data->buffer_size) goto fail; - data->op.bytes = read_streamfile(data->buffer,offset+header_size,packet_size, streamFile); - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */ - offset += header_size + packet_size; - } - else { - /* rebuild headers */ - - /* new identificacion packet */ - data->op.bytes = build_header_identification(data->buffer, data->buffer_size, cfg.channels, cfg.sample_rate, cfg.blocksize_0_exp, cfg.blocksize_1_exp); - if (!data->op.bytes) goto fail; - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */ - - /* new comment packet */ - data->op.bytes = build_header_comment(data->buffer, data->buffer_size); - if (!data->op.bytes) goto fail; - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */ - - /* rebuild setup packet */ - data->op.bytes = rebuild_setup(data->buffer, data->buffer_size, streamFile, start_offset, data, cfg.big_endian, cfg.channels); - if (!data->op.bytes) goto fail; - if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */ - } - - return 1; - -fail: - return 0; -} - - -int vorbis_custom_parse_packet_wwise(VGMSTREAMCHANNEL *stream, vorbis_custom_codec_data *data) { - size_t header_size, packet_size = 0; - - /* reconstruct a Wwise packet, if needed; final bytes may be bigger than packet_size so we get the header offsets here */ - header_size = get_packet_header(stream->streamfile, stream->offset, data->config.header_type, (int*)&data->op.granulepos, &packet_size, data->config.big_endian); - if (!header_size || packet_size > data->buffer_size) goto fail; - - data->op.bytes = rebuild_packet(data->buffer, data->buffer_size, stream->streamfile,stream->offset, data, data->config.big_endian); - stream->offset += header_size + packet_size; - if (!data->op.bytes || data->op.bytes >= 0xFFFF) goto fail; - - return 1; - -fail: - return 0; -} - -/* **************************************************************************** */ -/* INTERNAL HELPERS */ -/* **************************************************************************** */ - -/* loads info from a wwise packet header */ -static size_t get_packet_header(STREAMFILE *streamFile, off_t offset, wwise_header_t header_type, int * granulepos, size_t * packet_size, int big_endian) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = big_endian ? read_32bitBE : read_32bitLE; - int16_t (*read_16bit)(off_t,STREAMFILE*) = big_endian ? read_16bitBE : read_16bitLE; - - /* packet size doesn't include header size */ - switch(header_type) { - case WWV_TYPE_8: /* size 4+4 */ - *packet_size = (uint32_t)read_32bit(offset, streamFile); - *granulepos = read_32bit(offset+4, streamFile); - return 8; - - case WWV_TYPE_6: /* size 4+2 */ - *packet_size = (uint16_t)read_16bit(offset, streamFile); - *granulepos = read_32bit(offset+2, streamFile); - return 6; - - case WWV_TYPE_2: /* size 2 */ - *packet_size = (uint16_t)read_16bit(offset, streamFile); - *granulepos = 0; /* granule is an arbitrary unit so we could use offset instead; libvorbis has no actually need it actually */ - return 2; - break; - default: /* ? */ - return 0; - } -} - -/* Transforms a Wwise data packet into a real Vorbis one (depending on config) */ -static size_t rebuild_packet(uint8_t * obuf, size_t obufsize, STREAMFILE *streamFile, off_t offset, vorbis_custom_codec_data * data, int big_endian) { - vgm_bitstream ow, iw; - int rc, granulepos; - size_t header_size, packet_size; - - size_t ibufsize = 0x8000; /* arbitrary max size of a setup packet */ - uint8_t ibuf[0x8000]; /* Wwise setup packet buffer */ - if (obufsize < ibufsize) goto fail; /* arbitrary expected min */ - - header_size = get_packet_header(streamFile, offset, data->config.header_type, &granulepos, &packet_size, big_endian); - if (!header_size || packet_size > obufsize) goto fail; - - /* load Wwise data into internal buffer */ - if (read_streamfile(ibuf,offset+header_size,packet_size, streamFile)!=packet_size) - goto fail; - - /* prepare helper structs */ - ow.buf = obuf; - ow.bufsize = obufsize; - ow.b_off = 0; - ow.mode = BITSTREAM_VORBIS; - - iw.buf = ibuf; - iw.bufsize = ibufsize; - iw.b_off = 0; - iw.mode = BITSTREAM_VORBIS; - - rc = ww2ogg_generate_vorbis_packet(&ow,&iw, streamFile,offset, data, big_endian); - if (!rc) goto fail; - - if (ow.b_off % 8 != 0) { - //VGM_LOG("Wwise Vorbis: didn't write exactly audio packet: 0x%lx + %li bits\n", ow.b_off / 8, ow.b_off % 8); - goto fail; - } - - - return ow.b_off / 8; -fail: - return 0; -} - - -/* Transforms a Wwise setup packet into a real Vorbis one (depending on config). */ -static size_t rebuild_setup(uint8_t * obuf, size_t obufsize, STREAMFILE *streamFile, off_t offset, vorbis_custom_codec_data * data, int big_endian, int channels) { - vgm_bitstream ow, iw; - int rc, granulepos; - size_t header_size, packet_size; - - size_t ibufsize = 0x8000; /* arbitrary max size of a setup packet */ - uint8_t ibuf[0x8000]; /* Wwise setup packet buffer */ - if (obufsize < ibufsize) goto fail; /* arbitrary expected min */ - - /* read Wwise packet header */ - header_size = get_packet_header(streamFile, offset, data->config.header_type, &granulepos, &packet_size, big_endian); - if (!header_size || packet_size > ibufsize) goto fail; - - /* load Wwise setup into internal buffer */ - if (read_streamfile(ibuf,offset+header_size,packet_size, streamFile)!=packet_size) - goto fail; - - /* prepare helper structs */ - ow.buf = obuf; - ow.bufsize = obufsize; - ow.b_off = 0; - ow.mode = BITSTREAM_VORBIS; - - iw.buf = ibuf; - iw.bufsize = ibufsize; - iw.b_off = 0; - iw.mode = BITSTREAM_VORBIS; - - rc = ww2ogg_generate_vorbis_setup(&ow,&iw, data, channels, packet_size, streamFile); - if (!rc) goto fail; - - if (ow.b_off % 8 != 0) { - //VGM_LOG("Wwise Vorbis: didn't write exactly setup packet: 0x%lx + %li bits\n", ow.b_off / 8, ow.b_off % 8); - goto fail; - } - - - return ow.b_off / 8; -fail: - return 0; -} - -static size_t build_header_identification(uint8_t * buf, size_t bufsize, int channels, int sample_rate, int blocksize_0_exp, int blocksize_1_exp) { - size_t bytes = 0x1e; - uint8_t blocksizes; - - if (bytes > bufsize) return 0; - - blocksizes = (blocksize_0_exp << 4) | (blocksize_1_exp); - - put_8bit (buf+0x00, 0x01); /* packet_type (id) */ - memcpy (buf+0x01, "vorbis", 6); /* id */ - put_32bitLE(buf+0x07, 0x00); /* vorbis_version (fixed) */ - put_8bit (buf+0x0b, channels); /* audio_channels */ - put_32bitLE(buf+0x0c, sample_rate); /* audio_sample_rate */ - put_32bitLE(buf+0x10, 0x00); /* bitrate_maximum (optional hint) */ - put_32bitLE(buf+0x14, 0x00); /* bitrate_nominal (optional hint) */ - put_32bitLE(buf+0x18, 0x00); /* bitrate_minimum (optional hint) */ - put_8bit (buf+0x1c, blocksizes); /* blocksize_0 + blocksize_1 nibbles */ - put_8bit (buf+0x1d, 0x01); /* framing_flag (fixed) */ - - return bytes; -} - -static size_t build_header_comment(uint8_t * buf, size_t bufsize) { - size_t bytes = 0x19; - - if (bytes > bufsize) return 0; - - put_8bit (buf+0x00, 0x03); /* packet_type (comments) */ - memcpy (buf+0x01, "vorbis", 6); /* id */ - put_32bitLE(buf+0x07, 0x09); /* vendor_length */ - memcpy (buf+0x0b, "vgmstream", 9); /* vendor_string */ - put_32bitLE(buf+0x14, 0x00); /* user_comment_list_length */ - put_8bit (buf+0x18, 0x01); /* framing_flag (fixed) */ - - return bytes; -} - -/* **************************************************************************** */ -/* INTERNAL WW2OGG STUFF */ -/* **************************************************************************** */ -/* The following code was mostly and manually converted from hcs's ww2ogg. - * Could be simplified but roughly tries to preserve the structure in case fixes have to be backported. - * - * Some validations are ommited (ex. read/write), as incorrect data should be rejected by libvorbis. - * To avoid GCC complaining all values are init to 0, and some that do need it are init again, for clarity. - * Reads/writes unsigned ints as most are bit values less than 32 and with no sign meaning. - */ - -/* Copy packet as-is or rebuild first byte if mod_packets is used. - * (ref: https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-720004.3) */ -static int ww2ogg_generate_vorbis_packet(vgm_bitstream * ow, vgm_bitstream * iw, STREAMFILE *streamFile, off_t offset, vorbis_custom_codec_data * data, int big_endian) { - int i,granule; - size_t header_size, packet_size, data_size; - - header_size = get_packet_header(streamFile,offset, data->config.header_type, &granule, &packet_size, big_endian); - if (!header_size || packet_size > iw->bufsize) goto fail; - - data_size = get_streamfile_size(streamFile);//todo get external data_size - - if (offset + header_size + packet_size > data_size) { - VGM_LOG("Wwise Vorbis: page header truncated\n"); - goto fail; - } - - /* this may happen in the first packet; maybe it's for the encoder delay but doesn't seem to affect libvorbis */ - //VGM_ASSERT(granule < 0, "Wwise Vorbis: negative granule %i @ 0x%lx\n", granule, offset); - - - if (data->config.packet_type == WWV_MODIFIED) { - /* rebuild first bits of packet type and window info (for the i-MDCT) */ - uint32_t packet_type = 0, mode_number = 0, remainder = 0; - - if (!data->mode_blockflag) { /* config error */ - VGM_LOG("Wwise Vorbis: didn't load mode_blockflag\n"); - goto fail; - } - - /* audio packet type */ - packet_type = 0; - w_bits(ow, 1, packet_type); - - /* collect this packet mode from the first byte */ - r_bits(iw, data->mode_bits,&mode_number); /* max 6b */ - w_bits(ow, data->mode_bits, mode_number); - r_bits(iw, 8-data->mode_bits,&remainder); - - /* adjust window info */ - if (data->mode_blockflag[mode_number]) { - /* long window: peek at next frame to find flags */ - off_t next_offset = offset + header_size + packet_size; - uint32_t next_blockflag = 0, prev_window_type = 0, next_window_type = 0; - - next_blockflag = 0; - /* check if more data / not eof */ - if (next_offset + header_size <= data_size) { - size_t next_header_size, next_packet_size; - int next_granule; - - next_header_size = get_packet_header(streamFile,next_offset, data->config.header_type, &next_granule, &next_packet_size, big_endian); - if (!next_header_size) goto fail; - - if (next_packet_size > 0) { - /* get next first byte to read next_mode_number */ - uint32_t next_mode_number; - uint8_t nbuf[1]; - vgm_bitstream nw; - - nw.buf = nbuf; - nw.bufsize = 1; - nw.b_off = 0; - nw.mode = BITSTREAM_VORBIS; - - - if (read_streamfile(nw.buf, next_offset + next_header_size, nw.bufsize, streamFile) != nw.bufsize) - goto fail; - - r_bits(&nw, data->mode_bits,&next_mode_number); /* max 6b */ - - next_blockflag = data->mode_blockflag[next_mode_number]; - } - } - - prev_window_type = data->prev_blockflag; - w_bits(ow, 1, prev_window_type); - - next_window_type = next_blockflag; - w_bits(ow, 1, next_window_type); - } - - data->prev_blockflag = data->mode_blockflag[mode_number]; /* save for next packet */ - - w_bits(ow, 8-data->mode_bits, remainder); /* this *isn't* byte aligned (ex. could be 10 bits written) */ - } - else { - /* normal packets: first byte unchanged */ - uint32_t c = 0; - - r_bits(iw, 8, &c); - w_bits(ow, 8, c); - } - - - /* remainder of packet (not byte-aligned when using mod_packets) */ - for (i = 1; i < packet_size; i++) { - uint32_t c = 0; - - r_bits(iw, 8, &c); - w_bits(ow, 8, c); - } - - /* remove trailing garbage bits */ - if (ow->b_off % 8 != 0) { - uint32_t padding = 0; - int padding_bits = 8 - (ow->b_off % 8); - - w_bits(ow, padding_bits, padding); - } - - - return 1; -fail: - return 0; -} - - -/* Rebuild a Wwise setup (simplified with removed stuff), recreating all six setup parts. - * (ref: https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-650004.2.4) */ -static int ww2ogg_generate_vorbis_setup(vgm_bitstream * ow, vgm_bitstream * iw, vorbis_custom_codec_data * data, int channels, size_t packet_size, STREAMFILE *streamFile) { - int i,j,k; - uint32_t codebook_count = 0, floor_count = 0, residue_count = 0; - uint32_t codebook_count_less1 = 0; - uint32_t time_count_less1 = 0, dummy_time_value = 0; - - - /* packet header */ - put_8bit(ow->buf+0x00, 0x05); /* packet_type (setup) */ - memcpy (ow->buf+0x01, "vorbis", 6); /* id */ - ow->b_off += (1+6) * 8; /* bit offset of output (Vorbis) setup, after fake type + id */ - - - /* Codebooks */ - r_bits(iw, 8,&codebook_count_less1); - w_bits(ow, 8, codebook_count_less1); - codebook_count = codebook_count_less1 + 1; - - if (data->config.setup_type == WWV_FULL_SETUP) { - /* rebuild Wwise codebooks: untouched */ - for (i = 0; i < codebook_count; i++) { - if(!ww2ogg_codebook_library_copy(ow, iw)) goto fail; - } - } - else if (data->config.setup_type == WWV_INLINE_CODEBOOKS) { - /* rebuild Wwise codebooks: inline in simplified format */ - for (i = 0; i < codebook_count; i++) { - if(!ww2ogg_codebook_library_rebuild(ow, iw, 0, streamFile)) goto fail; - } - } - else { - /* rebuild Wwise codebooks: external (referenced by id) in simplified format */ - for (i = 0; i < codebook_count; i++) { - int rc; - uint32_t codebook_id = 0; - - r_bits(iw, 10,&codebook_id); - - rc = ww2ogg_codebook_library_rebuild_by_id(ow, codebook_id, data->config.setup_type, streamFile); - if (!rc) goto fail; - } - } - - - /* Time domain transforms */ - time_count_less1 = 0; - w_bits(ow, 6, time_count_less1); - dummy_time_value = 0; - w_bits(ow, 16, dummy_time_value); - - - if (data->config.setup_type == WWV_FULL_SETUP) { - /* rest of setup is untouched, copy bits */ - uint32_t bitly = 0; - uint32_t total_bits_read = iw->b_off; - uint32_t setup_packet_size_bits = packet_size*8; - - while (total_bits_read < setup_packet_size_bits) { - r_bits(iw, 1,&bitly); - w_bits(ow, 1, bitly); - total_bits_read = iw->b_off; - } - } - else { - /* rest of setup is altered, reconstruct */ - uint32_t floor_count_less1 = 0, floor1_multiplier_less1 = 0, rangebits = 0; - uint32_t residue_count_less1 = 0; - uint32_t mapping_count_less1 = 0, mapping_count = 0; - uint32_t mode_count_less1 = 0, mode_count = 0; - - - /* Floors */ - r_bits(iw, 6,&floor_count_less1); - w_bits(ow, 6, floor_count_less1); - floor_count = floor_count_less1 + 1; - - for (i = 0; i < floor_count; i++) { - uint32_t floor_type = 0, floor1_partitions = 0; - uint32_t maximum_class = 0; - uint32_t floor1_partition_class_list[32]; /* max 5b */ - uint32_t floor1_class_dimensions_list[16+1]; /* max 4b+1 */ - - // Always floor type 1 - floor_type = 1; - w_bits(ow, 16, floor_type); - - r_bits(iw, 5,&floor1_partitions); - w_bits(ow, 5, floor1_partitions); - - memset(floor1_partition_class_list, 0, sizeof(uint32_t)*32); - - maximum_class = 0; - for (j = 0; j < floor1_partitions; j++) { - uint32_t floor1_partition_class = 0; - - r_bits(iw, 4,&floor1_partition_class); - w_bits(ow, 4, floor1_partition_class); - - floor1_partition_class_list[j] = floor1_partition_class; - - if (floor1_partition_class > maximum_class) - maximum_class = floor1_partition_class; - } - - memset(floor1_class_dimensions_list, 0, sizeof(uint32_t)*(16+1)); - - for (j = 0; j <= maximum_class; j++) { - uint32_t class_dimensions_less1 = 0, class_subclasses = 0; - - r_bits(iw, 3,&class_dimensions_less1); - w_bits(ow, 3, class_dimensions_less1); - - floor1_class_dimensions_list[j] = class_dimensions_less1 + 1; - - r_bits(iw, 2,&class_subclasses); - w_bits(ow, 2, class_subclasses); - - if (0 != class_subclasses) { - uint32_t masterbook = 0; - - r_bits(iw, 8,&masterbook); - w_bits(ow, 8, masterbook); - - if (masterbook >= codebook_count) { - VGM_LOG("Wwise Vorbis: invalid floor1 masterbook\n"); - goto fail; - } - } - - for (k = 0; k < (1U<= 0 && subclass_book >= codebook_count) { - VGM_LOG("Wwise Vorbis: invalid floor1 subclass book\n"); - goto fail; - } - } - } - - r_bits(iw, 2,&floor1_multiplier_less1); - w_bits(ow, 2, floor1_multiplier_less1); - - r_bits(iw, 4,&rangebits); - w_bits(ow, 4, rangebits); - - for (j = 0; j < floor1_partitions; j++) { - uint32_t current_class_number = 0; - - current_class_number = floor1_partition_class_list[j]; - for (k = 0; k < floor1_class_dimensions_list[current_class_number]; k++) { - uint32_t X = 0; /* max 4b (15) */ - - r_bits(iw, rangebits,&X); - w_bits(ow, rangebits, X); - } - } - } - - - /* Residues */ - r_bits(iw, 6,&residue_count_less1); - w_bits(ow, 6, residue_count_less1); - residue_count = residue_count_less1 + 1; - - for (i = 0; i < residue_count; i++) { - uint32_t residue_type = 0, residue_classifications = 0; - uint32_t residue_begin = 0, residue_end = 0, residue_partition_size_less1 = 0, residue_classifications_less1 = 0, residue_classbook = 0; - uint32_t residue_cascade[64+1]; /* 6b +1 */ - - r_bits(iw, 2,&residue_type); - w_bits(ow, 16, residue_type); /* 2b to 16b */ - - if (residue_type > 2) { - VGM_LOG("Wwise Vorbis: invalid residue type\n"); - goto fail; - } - - r_bits(iw, 24,&residue_begin); - w_bits(ow, 24, residue_begin); - r_bits(iw, 24,&residue_end); - w_bits(ow, 24, residue_end); - r_bits(iw, 24,&residue_partition_size_less1); - w_bits(ow, 24, residue_partition_size_less1); - r_bits(iw, 6,&residue_classifications_less1); - w_bits(ow, 6, residue_classifications_less1); - r_bits(iw, 8,&residue_classbook); - w_bits(ow, 8, residue_classbook); - residue_classifications = residue_classifications_less1 + 1; - - if (residue_classbook >= codebook_count) { - VGM_LOG("Wwise Vorbis: invalid residue classbook\n"); - goto fail; - } - - memset(residue_cascade, 0, sizeof(uint32_t)*(64+1)); - - for (j = 0; j < residue_classifications; j++) { - uint32_t high_bits = 0, low_bits = 0, bitflag = 0; - - high_bits = 0; - - r_bits(iw, 3,&low_bits); - w_bits(ow, 3, low_bits); - - r_bits(iw, 1,&bitflag); - w_bits(ow, 1, bitflag); - if (bitflag) { - r_bits(iw, 5,&high_bits); - w_bits(ow, 5, high_bits); - } - - residue_cascade[j] = high_bits * 8 + low_bits; - } - - for (j = 0; j < residue_classifications; j++) { - for (k = 0; k < 8; k++) { - if (residue_cascade[j] & (1 << k)) { - uint32_t residue_book = 0; - - r_bits(iw, 8,&residue_book); - w_bits(ow, 8, residue_book); - - if (residue_book >= codebook_count) { - VGM_LOG("Wwise Vorbis: invalid residue book\n"); - goto fail; - } - } - } - } - } - - - /* Mappings */ - r_bits(iw, 6,&mapping_count_less1); - w_bits(ow, 6, mapping_count_less1); - mapping_count = mapping_count_less1 + 1; - - for (i = 0; i < mapping_count; i++) { - uint32_t mapping_type = 0, submaps_flag = 0, submaps = 0, square_polar_flag = 0; - uint32_t mapping_reserved = 0; - - // always mapping type 0, the only one - mapping_type = 0; - w_bits(ow, 16, mapping_type); - - r_bits(iw, 1,&submaps_flag); - w_bits(ow, 1, submaps_flag); - - submaps = 1; - if (submaps_flag) { - uint32_t submaps_less1 = 0; - - r_bits(iw, 4,&submaps_less1); - w_bits(ow, 4, submaps_less1); - submaps = submaps_less1 + 1; - } - - r_bits(iw, 1,&square_polar_flag); - w_bits(ow, 1, square_polar_flag); - - if (square_polar_flag) { - uint32_t coupling_steps_less1 = 0, coupling_steps = 0; - - r_bits(iw, 8,&coupling_steps_less1); - w_bits(ow, 8, coupling_steps_less1); - coupling_steps = coupling_steps_less1 + 1; - - for (j = 0; j < coupling_steps; j++) { - uint32_t magnitude = 0, angle = 0; - int magnitude_bits = ww2ogg_tremor_ilog(channels-1); - int angle_bits = ww2ogg_tremor_ilog(channels-1); - - r_bits(iw, magnitude_bits,&magnitude); - w_bits(ow, magnitude_bits, magnitude); - r_bits(iw, angle_bits,&angle); - w_bits(ow, angle_bits, angle); - - if (angle == magnitude || magnitude >= channels || angle >= channels) { - VGM_LOG("Wwise Vorbis: invalid coupling (angle=%i, mag=%i, ch=%i)\n", angle, magnitude,channels); - goto fail; - } - } - } - - // a rare reserved field not removed by Ak! - r_bits(iw, 2,&mapping_reserved); - w_bits(ow, 2, mapping_reserved); - if (0 != mapping_reserved) { - VGM_LOG("Wwise Vorbis: mapping reserved field nonzero\n"); - goto fail; - } - - if (submaps > 1) { - for (j = 0; j < channels; j++) { - uint32_t mapping_mux = 0; - - r_bits(iw, 4,&mapping_mux); - w_bits(ow, 4, mapping_mux); - if (mapping_mux >= submaps) { - VGM_LOG("Wwise Vorbis: mapping_mux >= submaps\n"); - goto fail; - } - } - } - - for (j = 0; j < submaps; j++) { - uint32_t time_config = 0, floor_number = 0, residue_number = 0; - - // Another! Unused time domain transform configuration placeholder! - r_bits(iw, 8,&time_config); - w_bits(ow, 8, time_config); - - r_bits(iw, 8,&floor_number); - w_bits(ow, 8, floor_number); - if (floor_number >= floor_count) { - VGM_LOG("Wwise Vorbis: invalid floor mapping\n"); - goto fail; - } - - r_bits(iw, 8,&residue_number); - w_bits(ow, 8, residue_number); - if (residue_number >= residue_count) { - VGM_LOG("Wwise Vorbis: invalid residue mapping\n"); - goto fail; - } - } - } - - - /* Modes */ - r_bits(iw, 6,&mode_count_less1); - w_bits(ow, 6, mode_count_less1); - mode_count = mode_count_less1 + 1; - - memset(data->mode_blockflag, 0, sizeof(uint8_t)*(64+1)); /* up to max mode_count */ - data->mode_bits = ww2ogg_tremor_ilog(mode_count-1); /* for mod_packets */ - - for (i = 0; i < mode_count; i++) { - uint32_t block_flag = 0, windowtype = 0, transformtype = 0, mapping = 0; - - r_bits(iw, 1,&block_flag); - w_bits(ow, 1, block_flag); - - data->mode_blockflag[i] = (block_flag != 0); /* for mod_packets */ - - windowtype = 0; - transformtype = 0; - w_bits(ow, 16, windowtype); - w_bits(ow, 16, transformtype); - - r_bits(iw, 8,&mapping); - w_bits(ow, 8, mapping); - if (mapping >= mapping_count) { - VGM_LOG("Wwise Vorbis: invalid mode mapping\n"); - goto fail; - } - } - } - - - /* end flag */ - { - uint32_t framing = 0; - - framing = 1; - w_bits(ow, 1, framing); - } - - /* remove trailing garbage bits */ - if (ow->b_off % 8 != 0) { - uint32_t padding = 0; - int padding_bits = 8 - (ow->b_off % 8); - - w_bits(ow, padding_bits, padding); - } - - - return 1; -fail: - return 0; -} - - -/* copies Vorbis codebooks (untouched, but size uncertain) */ -static int ww2ogg_codebook_library_copy(vgm_bitstream * ow, vgm_bitstream * iw) { - int i; - uint32_t id = 0, dimensions = 0, entries = 0; - uint32_t ordered = 0, lookup_type = 0; - - r_bits(iw, 24,&id); - w_bits(ow, 24, id); - r_bits(iw, 16,&dimensions); - w_bits(ow, 16, dimensions); - r_bits(iw, 24,&entries); - w_bits(ow, 24, entries); - - if (0x564342 != id) { /* "VCB" */ - VGM_LOG("Wwise Vorbis: invalid codebook identifier\n"); - goto fail; - } - - /* codeword lengths */ - r_bits(iw, 1,&ordered); - w_bits(ow, 1, ordered); - if (ordered) { - uint32_t initial_length = 0, current_entry = 0; - - r_bits(iw, 5,&initial_length); - w_bits(ow, 5, initial_length); - - current_entry = 0; - while (current_entry < entries) { - uint32_t number = 0; - int number_bits = ww2ogg_tremor_ilog(entries-current_entry); - - r_bits(iw, number_bits,&number); - w_bits(ow, number_bits, number); - current_entry += number; - } - if (current_entry > entries) { - VGM_LOG("Wwise Vorbis: current_entry out of range\n"); - goto fail; - } - } - else { - uint32_t sparse = 0; - - r_bits(iw, 1,&sparse); - w_bits(ow, 1, sparse); - - for (i = 0; i < entries; i++) { - uint32_t present_bool = 0; - - present_bool = 1; - if (sparse) { - uint32_t present = 0; - - r_bits(iw, 1,&present); - w_bits(ow, 1, present); - - present_bool = (0 != present); - } - - if (present_bool) { - uint32_t codeword_length = 0; - - r_bits(iw, 5,&codeword_length); - w_bits(ow, 5, codeword_length); - } - } - } - - - /* lookup table */ - r_bits(iw, 4,&lookup_type); - w_bits(ow, 4, lookup_type); - - if (0 == lookup_type) { - //VGM_LOG("Wwise Vorbis: no lookup table\n"); - } - else if (1 == lookup_type) { - //VGM_LOG("Wwise Vorbis: lookup type 1\n"); - uint32_t quantvals = 0, min = 0, max = 0; - uint32_t value_length = 0, sequence_flag = 0; - - r_bits(iw, 32,&min); - w_bits(ow, 32, min); - r_bits(iw, 32,&max); - w_bits(ow, 32, max); - r_bits(iw, 4,&value_length); - w_bits(ow, 4, value_length); - r_bits(iw, 1,&sequence_flag); - w_bits(ow, 1, sequence_flag); - - quantvals = ww2ogg_tremor_book_maptype1_quantvals(entries, dimensions); - for (i = 0; i < quantvals; i++) { - uint32_t val = 0, val_bits = 0; - val_bits = value_length+1; - - r_bits(iw, val_bits,&val); - w_bits(ow, val_bits, val); - } - } - else if (2 == lookup_type) { - VGM_LOG("Wwise Vorbis: didn't expect lookup type 2\n"); - goto fail; - } - else { - VGM_LOG("Wwise Vorbis: invalid lookup type\n"); - goto fail; - } - - - return 1; -fail: - return 0; -} - - -/* rebuilds a Wwise codebook into a Vorbis codebook */ -static int ww2ogg_codebook_library_rebuild(vgm_bitstream * ow, vgm_bitstream * iw, size_t cb_size, STREAMFILE *streamFile) { - int i; - uint32_t id = 0, dimensions = 0, entries = 0; - uint32_t ordered = 0, lookup_type = 0; - - id = 0x564342; /* "VCB" */ - - w_bits(ow, 24, id); - r_bits(iw, 4,&dimensions); - w_bits(ow, 16, dimensions); /* 4 to 16 */ - r_bits(iw, 14,&entries); - w_bits(ow, 24, entries); /* 14 to 24*/ - - /* codeword lengths */ - r_bits(iw, 1,&ordered); - w_bits(ow, 1, ordered); - if (ordered) { - uint32_t initial_length = 0, current_entry = 0; - - r_bits(iw, 5,&initial_length); - w_bits(ow, 5, initial_length); - - current_entry = 0; - while (current_entry < entries) { - uint32_t number = 0; - int number_bits = ww2ogg_tremor_ilog(entries-current_entry); - - r_bits(iw, number_bits,&number); - w_bits(ow, number_bits, number); - current_entry += number; - } - if (current_entry > entries) { - VGM_LOG("Wwise Vorbis: current_entry out of range\n"); - goto fail; - } - } - else { - uint32_t codeword_length_length = 0, sparse = 0; - - r_bits(iw, 3,&codeword_length_length); - r_bits(iw, 1,&sparse); - w_bits(ow, 1, sparse); - - if (0 == codeword_length_length || 5 < codeword_length_length) { - VGM_LOG("Wwise Vorbis: nonsense codeword length\n"); - goto fail; - } - - for (i = 0; i < entries; i++) { - uint32_t present_bool = 0; - - present_bool = 1; - if (sparse) { - uint32_t present = 0; - - r_bits(iw, 1,&present); - w_bits(ow, 1, present); - - present_bool = (0 != present); - } - - if (present_bool) { - uint32_t codeword_length = 0; - - r_bits(iw, codeword_length_length,&codeword_length); - w_bits(ow, 5, codeword_length); /* max 7 (3b) to 5 */ - } - } - } - - - /* lookup table */ - r_bits(iw, 1,&lookup_type); - w_bits(ow, 4, lookup_type); /* 1 to 4 */ - - if (0 == lookup_type) { - //VGM_LOG("Wwise Vorbis: no lookup table\n"); - } - else if (1 == lookup_type) { - //VGM_LOG("Wwise Vorbis: lookup type 1\n"); - uint32_t quantvals = 0, min = 0, max = 0; - uint32_t value_length = 0, sequence_flag = 0; - - r_bits(iw, 32,&min); - w_bits(ow, 32, min); - r_bits(iw, 32,&max); - w_bits(ow, 32, max); - r_bits(iw, 4,&value_length); - w_bits(ow, 4, value_length); - r_bits(iw, 1,&sequence_flag); - w_bits(ow, 1, sequence_flag); - - quantvals = ww2ogg_tremor_book_maptype1_quantvals(entries, dimensions); - for (i = 0; i < quantvals; i++) { - uint32_t val = 0, val_bits = 0; - val_bits = value_length+1; - - r_bits(iw, val_bits,&val); - w_bits(ow, val_bits, val); - } - } - else if (2 == lookup_type) { - VGM_LOG("Wwise Vorbis: didn't expect lookup type 2\n"); - goto fail; - } - else { - VGM_LOG("Wwise Vorbis: invalid lookup type\n"); - goto fail; - } - - - /* check that we used exactly all bytes */ - /* note: if all bits are used in the last byte there will be one extra 0 byte */ - if ( 0 != cb_size && iw->b_off/8+1 != cb_size ) { - //VGM_LOG("Wwise Vorbis: codebook size mistach (expected 0x%x, wrote 0x%lx)\n", cb_size, iw->b_off/8+1); - goto fail; - } - - return 1; -fail: - return 0; -} - -/* rebuilds an external Wwise codebook referenced by id to a Vorbis codebook */ -static int ww2ogg_codebook_library_rebuild_by_id(vgm_bitstream * ow, uint32_t codebook_id, wwise_setup_t setup_type, STREAMFILE *streamFile) { - size_t ibufsize = 0x8000; /* arbitrary max size of a codebook */ - uint8_t ibuf[0x8000]; /* Wwise codebook buffer */ - size_t cb_size; - vgm_bitstream iw; - - cb_size = load_wvc(ibuf,ibufsize, codebook_id, setup_type, streamFile); - if (cb_size == 0) goto fail; - - iw.buf = ibuf; - iw.bufsize = ibufsize; - iw.b_off = 0; - iw.mode = BITSTREAM_VORBIS; - - return ww2ogg_codebook_library_rebuild(ow, &iw, cb_size, streamFile); -fail: - return 0; -} - - -/* fixed-point ilog from Xiph's Tremor */ -static int ww2ogg_tremor_ilog(unsigned int v) { - int ret=0; - while(v){ - ret++; - v>>=1; - } - return(ret); -} -/* quantvals-something from Xiph's Tremor */ -static unsigned int ww2ogg_tremor_book_maptype1_quantvals(unsigned int entries, unsigned int dimensions) { - /* get us a starting hint, we'll polish it below */ - int bits=ww2ogg_tremor_ilog(entries); - int vals=entries>>((bits-1)*(dimensions-1)/dimensions); - - while(1){ - unsigned long acc=1; - unsigned long acc1=1; - unsigned int i; - for(i=0;ientries){ - return(vals); - }else{ - if(acc>entries){ - vals--; - }else{ - vals++; - } - } - } -} - - -/* **************************************************************************** */ -/* INTERNAL UTILS */ -/* **************************************************************************** */ - -/* loads an external Wwise Vorbis Codebooks file (wvc) referenced by ID and returns size */ -static int load_wvc(uint8_t * ibuf, size_t ibufsize, uint32_t codebook_id, wwise_setup_t setup_type, STREAMFILE *streamFile) { - size_t bytes; - - /* try to locate from the precompiled list */ - bytes = load_wvc_array(ibuf, ibufsize, codebook_id, setup_type); - if (bytes) - return bytes; - - /* try to load from external file (ignoring type, just use file if found) */ - bytes = load_wvc_file(ibuf, ibufsize, codebook_id, streamFile); - if (bytes) - return bytes; - - /* not found */ - VGM_LOG("Wwise Vorbis: codebook_id %04x not found\n", codebook_id); - return 0; -} - -static int load_wvc_file(uint8_t * buf, size_t bufsize, uint32_t codebook_id, STREAMFILE *streamFile) { - STREAMFILE * streamFileWvc = NULL; - size_t wvc_size = 0; - - { - char setupname[PATH_LIMIT]; - char pathname[PATH_LIMIT]; - char *path; - - /* read "(dir/).wvc" */ - streamFile->get_name(streamFile,pathname,sizeof(pathname)); - path = strrchr(pathname,DIR_SEPARATOR); - if (path) - *(path+1) = '\0'; - else - pathname[0] = '\0'; - - snprintf(setupname,PATH_LIMIT,"%s.wvc", pathname); - streamFileWvc = streamFile->open(streamFile,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!streamFileWvc) goto fail; - - wvc_size = streamFileWvc->get_size(streamFileWvc); - } - - /* find codebook and copy to buffer */ - { - off_t table_start, codebook_offset; - size_t codebook_size; - int codebook_count; - - /* at the end of the WVC is an offset table, and we need to find codebook id (number) offset */ - table_start = read_32bitLE(wvc_size - 4, streamFileWvc); /* last offset */ - codebook_count = ((wvc_size - table_start) / 4) - 1; - if (table_start > wvc_size || codebook_id >= codebook_count) goto fail; - - codebook_offset = read_32bitLE(table_start + codebook_id*4, streamFileWvc); - codebook_size = read_32bitLE(table_start + codebook_id*4 + 4, streamFileWvc) - codebook_offset; - if (codebook_size > bufsize) goto fail; - - if (read_streamfile(buf, codebook_offset, codebook_size, streamFileWvc) != codebook_size) - goto fail; - streamFileWvc->close(streamFileWvc); - - return codebook_size; - } - - -fail: - if (streamFileWvc) streamFileWvc->close(streamFileWvc); - return 0; -} - -static int load_wvc_array(uint8_t * buf, size_t bufsize, uint32_t codebook_id, wwise_setup_t setup_type) { -#if WWISE_VORBIS_USE_PRECOMPILED_WVC - - /* get pointer to array */ - { - int i, list_length; - const wvc_info * wvc_list; - - switch (setup_type) { - case WWV_EXTERNAL_CODEBOOKS: - wvc_list = wvc_list_standard; - list_length = sizeof(wvc_list_standard) / sizeof(wvc_info); - break; - case WWV_AOTUV603_CODEBOOKS: - wvc_list = wvc_list_aotuv603; - list_length = sizeof(wvc_list_standard) / sizeof(wvc_info); - break; - default: - goto fail; - } - - for (i=0; i < list_length; i++) { - if (wvc_list[i].id == codebook_id) { - if (wvc_list[i].size > bufsize) goto fail; - /* found: copy data as-is */ - memcpy(buf,wvc_list[i].codebook, wvc_list[i].size); - return wvc_list[i].size; - } - } - } - - // this can be used if the lists contained a 1:1 dump of the codebook files -#if 0 - if (wvc == NULL) goto fail; - /* find codebook and copy to buffer */ - { - off_t table_start, codebook_offset; - size_t codebook_size; - int codebook_count; - - /* at the end of the WVC is an offset table, and we need to find codebook id (number) offset */ - table_start = get_32bitLE(wvc + wvc_size - 4); /* last offset */ - codebook_count = ((wvc_size - table_start) / 4) - 1; - if (codebook_id >= codebook_count) goto fail; - - codebook_offset = get_32bitLE(wvc + table_start + codebook_id*4); - codebook_size = get_32bitLE(wvc + table_start + codebook_id*4 + 4) - codebook_offset; - if (codebook_size > bufsize) goto fail; - - memcpy(buf, wvc+codebook_offset, codebook_size); - - return codebook_size; - } -#endif - -fail: -#endif - return 0; -} - -#endif +#include "vorbis_custom_decoder.h" +#include "vorbis_bitreader.h" + +#ifdef VGM_USE_VORBIS +#include + +#define WWISE_VORBIS_USE_PRECOMPILED_WVC 1 /* if enabled vgmstream weights ~150kb more but doesn't need external .wvc packets */ +#if WWISE_VORBIS_USE_PRECOMPILED_WVC +#include "vorbis_custom_data_wwise.h" +#endif + + +/* **************************************************************************** */ +/* DEFS */ +/* **************************************************************************** */ + +typedef struct { + size_t header_size; + size_t packet_size; + int granulepos; + + int has_next; + uint8_t inxt[0x01]; +} wpacket_t; + +static size_t build_header_identification(uint8_t* buf, size_t bufsize, vorbis_custom_config* cfg); +static size_t build_header_comment(uint8_t* buf, size_t bufsize); + +static int read_packet(wpacket_t* wp, uint8_t* ibuf, size_t ibufsize, STREAMFILE* sf, off_t offset, vorbis_custom_codec_data* data, int is_setup); +static size_t rebuild_packet(uint8_t* obuf, size_t obufsize, wpacket_t* wp, STREAMFILE* sf, off_t offset, vorbis_custom_codec_data* data); +static size_t rebuild_setup(uint8_t* obuf, size_t obufsize, wpacket_t* wp, STREAMFILE* sf, off_t offset, vorbis_custom_codec_data* data); + +static int ww2ogg_generate_vorbis_packet(bitstream_t* ow, bitstream_t* iw, wpacket_t* wp, vorbis_custom_codec_data* data); +static int ww2ogg_generate_vorbis_setup(bitstream_t* ow, bitstream_t* iw, vorbis_custom_codec_data* data, size_t packet_size, STREAMFILE* sf); + +static int load_wvc(uint8_t* ibuf, size_t ibufsize, uint32_t codebook_id, wwise_setup_t setup_type, STREAMFILE* sf); +static int load_wvc_file(uint8_t* buf, size_t bufsize, uint32_t codebook_id, STREAMFILE* sf); +static int load_wvc_array(uint8_t* buf, size_t bufsize, uint32_t codebook_id, wwise_setup_t setup_type); + + +/* **************************************************************************** */ +/* EXTERNAL API */ +/* **************************************************************************** */ + +/** + * Wwise stores a reduced setup, and packets have mini headers with the size, and data packets + * may reduced as well. The format evolved over time so there are many variations. + * The Wwise implementation uses Tremor (fixed-point Vorbis) but shouldn't matter. + * + * Format reverse-engineered by hcs in ww2ogg (https://github.com/hcs64/ww2ogg). + */ +int vorbis_custom_setup_init_wwise(STREAMFILE* sf, off_t start_offset, vorbis_custom_codec_data* data) { + wpacket_t wp = {0}; + int ok; + + + if (data->config.setup_type == WWV_HEADER_TRIAD) { + /* read 3 Wwise packets with triad (id/comment/setup), each with a Wwise header */ + off_t offset = start_offset; + + /* normal identificacion packet */ + ok = read_packet(&wp, data->buffer, data->buffer_size, sf, offset, data, 1); + if (!ok) goto fail; + data->op.bytes = wp.packet_size; + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; + offset += wp.header_size + wp.packet_size; + + /* normal comment packet */ + ok = read_packet(&wp, data->buffer, data->buffer_size, sf, offset, data, 1); + if (!ok) goto fail; + data->op.bytes = wp.packet_size; + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; + offset += wp.header_size + wp.packet_size; + + /* normal setup packet */ + ok = read_packet(&wp, data->buffer, data->buffer_size, sf, offset, data, 1); + if (!ok) goto fail; + data->op.bytes = wp.packet_size; + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; + offset += wp.header_size + wp.packet_size; + } + else { + /* rebuild headers */ + + /* new identificacion packet */ + data->op.bytes = build_header_identification(data->buffer, data->buffer_size, &data->config); + if (!data->op.bytes) goto fail; + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse identification header */ + + /* new comment packet */ + data->op.bytes = build_header_comment(data->buffer, data->buffer_size); + if (!data->op.bytes) goto fail; + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) !=0 ) goto fail; /* parse comment header */ + + /* rebuild setup packet */ + data->op.bytes = rebuild_setup(data->buffer, data->buffer_size, &wp, sf, start_offset, data); + if (!data->op.bytes) goto fail; + if (vorbis_synthesis_headerin(&data->vi, &data->vc, &data->op) != 0) goto fail; /* parse setup header */ + } + + return 1; + +fail: + return 0; +} + + +int vorbis_custom_parse_packet_wwise(VGMSTREAMCHANNEL* stream, vorbis_custom_codec_data* data) { + wpacket_t wp = {0}; + + /* reconstruct a Wwise packet, if needed; final bytes may be bigger than packet_size */ + data->op.bytes = rebuild_packet(data->buffer, data->buffer_size, &wp, stream->streamfile, stream->offset, data); + stream->offset += wp.header_size + wp.packet_size; + if (!data->op.bytes || data->op.bytes >= 0xFFFF) goto fail; + + data->op.granulepos = wp.granulepos; + + return 1; + +fail: + return 0; +} + +/* **************************************************************************** */ +/* INTERNAL HELPERS */ +/* **************************************************************************** */ + +static int read_packet(wpacket_t* wp, uint8_t* ibuf, size_t ibufsize, STREAMFILE* sf, off_t offset, vorbis_custom_codec_data* data, int is_setup) { + uint32_t (*get_u32)(const uint8_t*) = data->config.big_endian ? get_u32be : get_u32le; + uint16_t (*get_u16)(const uint8_t*) = data->config.big_endian ? get_u16be : get_u16le; + int32_t (*get_s32)(const uint8_t*) = data->config.big_endian ? get_s32be : get_s32le; + + /* read header info (packet size doesn't include header size) */ + switch(data->config.header_type) { + case WWV_TYPE_8: + wp->header_size = 0x08; + read_streamfile(ibuf, offset, wp->header_size, sf); + wp->packet_size = get_u32(ibuf + 0x00); + wp->granulepos = get_s32(ibuf + 0x04); + break; + + case WWV_TYPE_6: + wp->header_size = 0x06; + read_streamfile(ibuf, offset, wp->header_size, sf); + wp->packet_size = get_u16(ibuf + 0x00); + wp->granulepos = get_s32(ibuf + 0x02); + break; + + case WWV_TYPE_2: + wp->header_size = 0x02; + read_streamfile(ibuf, offset, wp->header_size, sf); + wp->packet_size = get_u16(ibuf + 0x00); + wp->granulepos = 0; /* granule is an arbitrary unit so we could use offset instead; libvorbis has no need for it */ + break; + + default: /* ? */ + wp->header_size = 0; + wp->packet_size = 0; + wp->granulepos = 0; + break; + } + + /* read packet data */ + { + size_t read_size = wp->packet_size; + size_t read; + + /* mod packets need next packet's first byte (6 bits) except at EOF, so read now too */ + if (!is_setup && data->config.packet_type == WWV_MODIFIED) { + read_size += wp->header_size + 0x01; + } + + if (!wp->header_size || read_size > ibufsize) + goto fail; + + read = read_streamfile(ibuf, offset + wp->header_size, read_size, sf); + if (read < wp->packet_size) { + VGM_LOG("Wwise Vorbis: truncated packet\n"); + goto fail; + } + + if (!is_setup && data->config.packet_type == WWV_MODIFIED && read == read_size) { + wp->has_next = 1; + wp->inxt[0] = ibuf[wp->packet_size + wp->header_size]; + } + else { + wp->has_next = 0; + } + } + + return 1; +fail: + return 0; +} + + +/* Transforms a Wwise data packet into a real Vorbis one (depending on config) */ +static size_t rebuild_packet(uint8_t* obuf, size_t obufsize, wpacket_t* wp, STREAMFILE* sf, off_t offset, vorbis_custom_codec_data* data) { + bitstream_t ow, iw; + int ok; + uint8_t ibuf[0x8000]; /* arbitrary max */ + size_t ibufsize = sizeof(ibuf); + + if (obufsize < ibufsize) /* arbitrary min */ + goto fail; + + ok = read_packet(wp, ibuf, ibufsize, sf, offset, data, 0); + if (!ok) goto fail; + + init_bitstream(&ow, obuf, obufsize); + init_bitstream(&iw, ibuf, ibufsize); + + ok = ww2ogg_generate_vorbis_packet(&ow, &iw, wp, data); + if (!ok) goto fail; + + if (ow.b_off % 8 != 0) { + //VGM_LOG("Wwise Vorbis: didn't write exactly audio packet: 0x%lx + %li bits\n", ow.b_off / 8, ow.b_off % 8); + goto fail; + } + + + return ow.b_off / 8; +fail: + return 0; +} + +/* Transforms a Wwise setup packet into a real Vorbis one (depending on config). */ +static size_t rebuild_setup(uint8_t* obuf, size_t obufsize, wpacket_t* wp, STREAMFILE* sf, off_t offset, vorbis_custom_codec_data* data) { + bitstream_t ow, iw; + int ok; + uint8_t ibuf[0x8000]; /* arbitrary max */ + size_t ibufsize = sizeof(ibuf); + + if (obufsize < ibufsize) /* arbitrary min */ + goto fail; + + ok = read_packet(wp, ibuf, ibufsize, sf, offset, data, 1); + if (!ok) goto fail; + + init_bitstream(&ow, obuf, obufsize); + init_bitstream(&iw, ibuf, ibufsize); + + ok = ww2ogg_generate_vorbis_setup(&ow,&iw, data, wp->packet_size, sf); + if (!ok) goto fail; + + if (ow.b_off % 8 != 0) { + //VGM_LOG("Wwise Vorbis: didn't write exactly setup packet: 0x%lx + %li bits\n", ow.b_off / 8, ow.b_off % 8); + goto fail; + } + + + return ow.b_off / 8; +fail: + return 0; +} + +static size_t build_header_identification(uint8_t* buf, size_t bufsize, vorbis_custom_config* cfg) { + size_t bytes = 0x1e; + uint8_t blocksizes; + + if (bytes > bufsize) return 0; + + blocksizes = (cfg->blocksize_0_exp << 4) | (cfg->blocksize_1_exp); + + put_8bit (buf+0x00, 0x01); /* packet_type (id) */ + memcpy (buf+0x01, "vorbis", 6); /* id */ + put_32bitLE(buf+0x07, 0x00); /* vorbis_version (fixed) */ + put_8bit (buf+0x0b, cfg->channels); /* audio_channels */ + put_32bitLE(buf+0x0c, cfg->sample_rate);/* audio_sample_rate */ + put_32bitLE(buf+0x10, 0x00); /* bitrate_maximum (optional hint) */ + put_32bitLE(buf+0x14, 0x00); /* bitrate_nominal (optional hint) */ + put_32bitLE(buf+0x18, 0x00); /* bitrate_minimum (optional hint) */ + put_8bit (buf+0x1c, blocksizes); /* blocksize_0 + blocksize_1 nibbles */ + put_8bit (buf+0x1d, 0x01); /* framing_flag (fixed) */ + + return bytes; +} + +static size_t build_header_comment(uint8_t* buf, size_t bufsize) { + size_t bytes = 0x19; + + if (bytes > bufsize) return 0; + + put_8bit (buf+0x00, 0x03); /* packet_type (comments) */ + memcpy (buf+0x01, "vorbis", 6); /* id */ + put_32bitLE(buf+0x07, 0x09); /* vendor_length */ + memcpy (buf+0x0b, "vgmstream", 9); /* vendor_string */ + put_32bitLE(buf+0x14, 0x00); /* user_comment_list_length */ + put_8bit (buf+0x18, 0x01); /* framing_flag (fixed) */ + + return bytes; +} + + +/* copy packet bytes, where input/output bufs may not be byte-aligned (so no memcpy) */ +static int copy_bytes(bitstream_t* ob, bitstream_t* ib, uint32_t bytes) { + int i; + +#if 0 + /* in theory this would be faster, but not clear results; maybe default is just optimized by compiler */ + for (i = 0; i < bytes / 4; i++) { + uint32_t c = 0; + + rv_bits(ib, 32, &c); + wv_bits(ob, 32, c); + } + for (i = 0; i < bytes % 4; i++) { + uint32_t c = 0; + + rv_bits(ib, 8, &c); + wv_bits(ob, 8, c); + } +#endif + +#if 0 + /* output bits are never(?) byte aligned but input always is, yet this doesn't seem any faster */ + if (ib->b_off % 8 == 0) { + int iw_pos = ib->b_off / 8; + + for (i = 0; i < bytes; i++, iw_pos++) { + uint32_t c = ib->buf[iw_pos]; + + //rv_bits(ib, 8, &c); + wv_bits(ob, 8, c); + } + + ib->b_off += bytes * 8; + return 1; + } +#endif + + for (i = 0; i < bytes; i++) { + uint32_t c = 0; + + rv_bits(ib, 8, &c); + wv_bits(ob, 8, c); + } + + return 1; +} + +/* **************************************************************************** */ +/* INTERNAL WW2OGG STUFF */ +/* **************************************************************************** */ +/* The following code was mostly and manually converted from hcs's ww2ogg (https://github.com/hcs64/ww2ogg). + * Could be simplified but roughly tries to preserve the structure for comparison. + * + * Some validations are ommited (ex. read/write), as incorrect data should be rejected by libvorbis. + * To avoid GCC complaining all values are init to 0, and some that do need it are init again, for clarity. + * Reads/writes unsigned ints as most are bit values less than 32 and with no sign meaning. + */ + +/* Copy packet as-is or rebuild to standard Vorbis packet if mod_packets is used. + * (ref: https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-720004.3) */ +static int ww2ogg_generate_vorbis_packet(bitstream_t* ow, bitstream_t* iw, wpacket_t* wp, vorbis_custom_codec_data* data) { + + /* this may happen in the first packet; maybe it's for the encoder delay but doesn't seem to affect libvorbis */ + //VGM_ASSERT(granule < 0, "Wwise Vorbis: negative granule %i @ 0x%lx\n", granule, offset); + + + if (data->config.packet_type == WWV_MODIFIED) { + /* rebuild first bits of packet type and window info (for the i-MDCT) */ + uint32_t packet_type = 0, mode_number = 0, remainder = 0; + + if (!data->mode_blockflag) { /* config error */ + VGM_LOG("Wwise Vorbis: didn't load mode_blockflag\n"); + goto fail; + } + + /* audio packet type */ + packet_type = 0; + wv_bits(ow, 1, packet_type); + + /* collect this packet mode from the first byte */ + rv_bits(iw, data->mode_bits,&mode_number); /* max 6b */ + wv_bits(ow, data->mode_bits, mode_number); + + rv_bits(iw, 8-data->mode_bits,&remainder); + + /* adjust window info */ + if (data->mode_blockflag[mode_number]) { + /* long window: peek at next frame to find flags */ + uint32_t next_blockflag = 0, prev_window_type = 0, next_window_type = 0; + + if (wp->has_next) { + /* get next first byte to read next_mode_number */ + uint32_t next_mode_number; + bitstream_t nw; + + init_bitstream(&nw, wp->inxt, sizeof(wp->inxt)); + + rv_bits(&nw, data->mode_bits,&next_mode_number); /* max 6b */ + + next_blockflag = data->mode_blockflag[next_mode_number]; + } + else { + /* EOF (probably doesn't matter) */ + next_blockflag = 0; + } + + prev_window_type = data->prev_blockflag; + wv_bits(ow, 1, prev_window_type); + + next_window_type = next_blockflag; + wv_bits(ow, 1, next_window_type); + } + + data->prev_blockflag = data->mode_blockflag[mode_number]; /* save for next packet */ + + wv_bits(ow, 8-data->mode_bits, remainder); + + /* rest of the packet (input/output bytes aren't byte aligned here, so no memcpy) */ + copy_bytes(ow, iw, wp->packet_size - 1); + + /* remove trailing garbage bits (probably unneeded) */ + if (ow->b_off % 8 != 0) { + uint32_t padding = 0; + int padding_bits = 8 - (ow->b_off % 8); + + wv_bits(ow, padding_bits, padding); + } + } + else { + /* normal packets */ + + /* can directly copy (much, much faster), but least common case vs the above... */ + memcpy(ow->buf + ow->b_off / 8, iw->buf + iw->b_off / 8, wp->packet_size); + ow->b_off += wp->packet_size * 8; + iw->b_off += wp->packet_size * 8; + } + + + return 1; +fail: + return 0; +} + +/*******************************************************************************/ + +/* fixed-point ilog from Xiph's Tremor */ +static int ww2ogg_tremor_ilog(unsigned int v) { + int ret=0; + while(v){ + ret++; + v>>=1; + } + return(ret); +} + +/* quantvals-something from Xiph's Tremor */ +static unsigned int ww2ogg_tremor_book_maptype1_quantvals(unsigned int entries, unsigned int dimensions) { + /* get us a starting hint, we'll polish it below */ + int bits=ww2ogg_tremor_ilog(entries); + int vals=entries>>((bits-1)*(dimensions-1)/dimensions); + + while(1){ + unsigned long acc=1; + unsigned long acc1=1; + unsigned int i; + for(i=0;ientries){ + return(vals); + }else{ + if(acc>entries){ + vals--; + }else{ + vals++; + } + } + } +} + + +/* copies Vorbis codebooks (untouched, but size uncertain) */ +static int ww2ogg_codebook_library_copy(bitstream_t* ow, bitstream_t* iw) { + int i; + uint32_t id = 0, dimensions = 0, entries = 0; + uint32_t ordered = 0, lookup_type = 0; + + rv_bits(iw, 24,&id); + wv_bits(ow, 24, id); + rv_bits(iw, 16,&dimensions); + wv_bits(ow, 16, dimensions); + rv_bits(iw, 24,&entries); + wv_bits(ow, 24, entries); + + if (0x564342 != id) { /* "VCB" */ + VGM_LOG("Wwise Vorbis: invalid codebook identifier\n"); + goto fail; + } + + /* codeword lengths */ + rv_bits(iw, 1,&ordered); + wv_bits(ow, 1, ordered); + if (ordered) { + uint32_t initial_length = 0, current_entry = 0; + + rv_bits(iw, 5,&initial_length); + wv_bits(ow, 5, initial_length); + + current_entry = 0; + while (current_entry < entries) { + uint32_t number = 0; + int numberv_bits = ww2ogg_tremor_ilog(entries-current_entry); + + rv_bits(iw, numberv_bits,&number); + wv_bits(ow, numberv_bits, number); + current_entry += number; + } + if (current_entry > entries) { + VGM_LOG("Wwise Vorbis: current_entry out of range\n"); + goto fail; + } + } + else { + uint32_t sparse = 0; + + rv_bits(iw, 1,&sparse); + wv_bits(ow, 1, sparse); + + for (i = 0; i < entries; i++) { + uint32_t present_bool = 0; + + present_bool = 1; + if (sparse) { + uint32_t present = 0; + + rv_bits(iw, 1,&present); + wv_bits(ow, 1, present); + + present_bool = (0 != present); + } + + if (present_bool) { + uint32_t codeword_length = 0; + + rv_bits(iw, 5,&codeword_length); + wv_bits(ow, 5, codeword_length); + } + } + } + + + /* lookup table */ + rv_bits(iw, 4,&lookup_type); + wv_bits(ow, 4, lookup_type); + + if (0 == lookup_type) { + //VGM_LOG("Wwise Vorbis: no lookup table\n"); + } + else if (1 == lookup_type) { + //VGM_LOG("Wwise Vorbis: lookup type 1\n"); + uint32_t quantvals = 0, min = 0, max = 0; + uint32_t value_length = 0, sequence_flag = 0; + + rv_bits(iw, 32,&min); + wv_bits(ow, 32, min); + rv_bits(iw, 32,&max); + wv_bits(ow, 32, max); + rv_bits(iw, 4,&value_length); + wv_bits(ow, 4, value_length); + rv_bits(iw, 1,&sequence_flag); + wv_bits(ow, 1, sequence_flag); + + quantvals = ww2ogg_tremor_book_maptype1_quantvals(entries, dimensions); + for (i = 0; i < quantvals; i++) { + uint32_t val = 0, val_bits = 0; + val_bits = value_length+1; + + rv_bits(iw, val_bits,&val); + wv_bits(ow, val_bits, val); + } + } + else if (2 == lookup_type) { + VGM_LOG("Wwise Vorbis: didn't expect lookup type 2\n"); + goto fail; + } + else { + VGM_LOG("Wwise Vorbis: invalid lookup type\n"); + goto fail; + } + + return 1; +fail: + return 0; +} + +/* rebuilds a Wwise codebook into a Vorbis codebook */ +static int ww2ogg_codebook_library_rebuild(bitstream_t* ow, bitstream_t* iw, size_t cb_size, STREAMFILE* sf) { + int i; + uint32_t id = 0, dimensions = 0, entries = 0; + uint32_t ordered = 0, lookup_type = 0; + + id = 0x564342; /* "VCB" */ + + wv_bits(ow, 24, id); + rv_bits(iw, 4,&dimensions); + wv_bits(ow, 16, dimensions); /* 4 to 16 */ + rv_bits(iw, 14,&entries); + wv_bits(ow, 24, entries); /* 14 to 24*/ + + /* codeword lengths */ + rv_bits(iw, 1,&ordered); + wv_bits(ow, 1, ordered); + if (ordered) { + uint32_t initial_length = 0, current_entry = 0; + + rv_bits(iw, 5,&initial_length); + wv_bits(ow, 5, initial_length); + + current_entry = 0; + while (current_entry < entries) { + uint32_t number = 0; + int numberv_bits = ww2ogg_tremor_ilog(entries-current_entry); + + rv_bits(iw, numberv_bits,&number); + wv_bits(ow, numberv_bits, number); + current_entry += number; + } + if (current_entry > entries) { + VGM_LOG("Wwise Vorbis: current_entry out of range\n"); + goto fail; + } + } + else { + uint32_t codeword_length_length = 0, sparse = 0; + + rv_bits(iw, 3,&codeword_length_length); + rv_bits(iw, 1,&sparse); + wv_bits(ow, 1, sparse); + + if (0 == codeword_length_length || 5 < codeword_length_length) { + VGM_LOG("Wwise Vorbis: nonsense codeword length\n"); + goto fail; + } + + for (i = 0; i < entries; i++) { + uint32_t present_bool = 0; + + present_bool = 1; + if (sparse) { + uint32_t present = 0; + + rv_bits(iw, 1,&present); + wv_bits(ow, 1, present); + + present_bool = (0 != present); + } + + if (present_bool) { + uint32_t codeword_length = 0; + + rv_bits(iw, codeword_length_length,&codeword_length); + wv_bits(ow, 5, codeword_length); /* max 7 (3b) to 5 */ + } + } + } + + + /* lookup table */ + rv_bits(iw, 1,&lookup_type); + wv_bits(ow, 4, lookup_type); /* 1 to 4 */ + + if (0 == lookup_type) { + //VGM_LOG("Wwise Vorbis: no lookup table\n"); + } + else if (1 == lookup_type) { + //VGM_LOG("Wwise Vorbis: lookup type 1\n"); + uint32_t quantvals = 0, min = 0, max = 0; + uint32_t value_length = 0, sequence_flag = 0; + + rv_bits(iw, 32,&min); + wv_bits(ow, 32, min); + rv_bits(iw, 32,&max); + wv_bits(ow, 32, max); + rv_bits(iw, 4,&value_length); + wv_bits(ow, 4, value_length); + rv_bits(iw, 1,&sequence_flag); + wv_bits(ow, 1, sequence_flag); + + quantvals = ww2ogg_tremor_book_maptype1_quantvals(entries, dimensions); + for (i = 0; i < quantvals; i++) { + uint32_t val = 0, val_bits = 0; + val_bits = value_length+1; + + rv_bits(iw, val_bits,&val); + wv_bits(ow, val_bits, val); + } + } + else if (2 == lookup_type) { + VGM_LOG("Wwise Vorbis: didn't expect lookup type 2\n"); + goto fail; + } + else { + VGM_LOG("Wwise Vorbis: invalid lookup type\n"); + goto fail; + } + + + /* check that we used exactly all bytes */ + /* note: if all bits are used in the last byte there will be one extra 0 byte */ + if ( 0 != cb_size && iw->b_off/8+1 != cb_size ) { + //VGM_LOG("Wwise Vorbis: codebook size mistach (expected 0x%x, wrote 0x%lx)\n", cb_size, iw->b_off/8+1); + goto fail; + } + + return 1; +fail: + return 0; +} + +/* rebuilds an external Wwise codebook referenced by id to a Vorbis codebook */ +static int ww2ogg_codebook_library_rebuild_by_id(bitstream_t* ow, uint32_t codebook_id, wwise_setup_t setup_type, STREAMFILE* sf) { + size_t ibufsize = 0x8000; /* arbitrary max size of a codebook */ + uint8_t ibuf[0x8000]; /* Wwise codebook buffer */ + size_t cb_size; + bitstream_t iw; + + cb_size = load_wvc(ibuf,ibufsize, codebook_id, setup_type, sf); + if (cb_size == 0) goto fail; + + init_bitstream(&iw, ibuf, ibufsize); + + return ww2ogg_codebook_library_rebuild(ow, &iw, cb_size, sf); +fail: + return 0; +} + +/* Rebuild a Wwise setup (simplified with removed stuff), recreating all six setup parts. + * (ref: https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-650004.2.4) */ +static int ww2ogg_generate_vorbis_setup(bitstream_t* ow, bitstream_t* iw, vorbis_custom_codec_data* data, size_t packet_size, STREAMFILE* sf) { + int i, j, k; + int channels = data->config.channels; + uint32_t codebook_count = 0, floor_count = 0, residue_count = 0; + uint32_t codebook_count_less1 = 0; + uint32_t time_count_less1 = 0, dummy_time_value = 0; + + + /* packet header */ + put_8bit(ow->buf+0x00, 0x05); /* packet_type (setup) */ + memcpy (ow->buf+0x01, "vorbis", 6); /* id */ + ow->b_off += (1+6) * 8; /* bit offset of output (Vorbis) setup, after fake type + id */ + + + /* Codebooks */ + rv_bits(iw, 8,&codebook_count_less1); + wv_bits(ow, 8, codebook_count_less1); + codebook_count = codebook_count_less1 + 1; + + if (data->config.setup_type == WWV_FULL_SETUP) { + /* rebuild Wwise codebooks: untouched */ + for (i = 0; i < codebook_count; i++) { + if (!ww2ogg_codebook_library_copy(ow, iw)) + goto fail; + } + } + else if (data->config.setup_type == WWV_INLINE_CODEBOOKS) { + /* rebuild Wwise codebooks: inline in simplified format */ + for (i = 0; i < codebook_count; i++) { + if (!ww2ogg_codebook_library_rebuild(ow, iw, 0, sf)) + goto fail; + } + } + else { + /* rebuild Wwise codebooks: external (referenced by id) in simplified format */ + for (i = 0; i < codebook_count; i++) { + int rc; + uint32_t codebook_id = 0; + + rv_bits(iw, 10,&codebook_id); + + rc = ww2ogg_codebook_library_rebuild_by_id(ow, codebook_id, data->config.setup_type, sf); + if (!rc) goto fail; + } + } + + + /* Time domain transforms */ + time_count_less1 = 0; + wv_bits(ow, 6, time_count_less1); + dummy_time_value = 0; + wv_bits(ow, 16, dummy_time_value); + + + if (data->config.setup_type == WWV_FULL_SETUP) { + /* rest of setup is untouched, copy bits */ + uint32_t bitly = 0; + uint32_t total_bits_read = iw->b_off; + uint32_t setup_packet_size_bits = packet_size * 8; + + while (total_bits_read < setup_packet_size_bits) { + rv_bits(iw, 1,&bitly); + wv_bits(ow, 1, bitly); + total_bits_read = iw->b_off; + } + } + else { + /* rest of setup is altered, reconstruct */ + uint32_t floor_count_less1 = 0, floor1_multiplier_less1 = 0, rangebits = 0; + uint32_t residue_count_less1 = 0; + uint32_t mapping_count_less1 = 0, mapping_count = 0; + uint32_t mode_count_less1 = 0, mode_count = 0; + + + /* Floors */ + rv_bits(iw, 6,&floor_count_less1); + wv_bits(ow, 6, floor_count_less1); + floor_count = floor_count_less1 + 1; + + for (i = 0; i < floor_count; i++) { + uint32_t floor_type = 0, floor1_partitions = 0; + uint32_t maximum_class = 0; + uint32_t floor1_partition_class_list[32]; /* max 5b */ + uint32_t floor1_class_dimensions_list[16+1]; /* max 4b+1 */ + + // Always floor type 1 + floor_type = 1; + wv_bits(ow, 16, floor_type); + + rv_bits(iw, 5,&floor1_partitions); + wv_bits(ow, 5, floor1_partitions); + + memset(floor1_partition_class_list, 0, sizeof(uint32_t)*32); + + maximum_class = 0; + for (j = 0; j < floor1_partitions; j++) { + uint32_t floor1_partition_class = 0; + + rv_bits(iw, 4,&floor1_partition_class); + wv_bits(ow, 4, floor1_partition_class); + + floor1_partition_class_list[j] = floor1_partition_class; + + if (floor1_partition_class > maximum_class) + maximum_class = floor1_partition_class; + } + + memset(floor1_class_dimensions_list, 0, sizeof(uint32_t)*(16+1)); + + for (j = 0; j <= maximum_class; j++) { + uint32_t class_dimensions_less1 = 0, class_subclasses = 0; + + rv_bits(iw, 3,&class_dimensions_less1); + wv_bits(ow, 3, class_dimensions_less1); + + floor1_class_dimensions_list[j] = class_dimensions_less1 + 1; + + rv_bits(iw, 2,&class_subclasses); + wv_bits(ow, 2, class_subclasses); + + if (0 != class_subclasses) { + uint32_t masterbook = 0; + + rv_bits(iw, 8,&masterbook); + wv_bits(ow, 8, masterbook); + + if (masterbook >= codebook_count) { + VGM_LOG("Wwise Vorbis: invalid floor1 masterbook\n"); + goto fail; + } + } + + for (k = 0; k < (1U<= 0 && subclass_book >= codebook_count) { + VGM_LOG("Wwise Vorbis: invalid floor1 subclass book\n"); + goto fail; + } + } + } + + rv_bits(iw, 2,&floor1_multiplier_less1); + wv_bits(ow, 2, floor1_multiplier_less1); + + rv_bits(iw, 4,&rangebits); + wv_bits(ow, 4, rangebits); + + for (j = 0; j < floor1_partitions; j++) { + uint32_t current_class_number = 0; + + current_class_number = floor1_partition_class_list[j]; + for (k = 0; k < floor1_class_dimensions_list[current_class_number]; k++) { + uint32_t X = 0; /* max 4b (15) */ + + rv_bits(iw, rangebits,&X); + wv_bits(ow, rangebits, X); + } + } + } + + + /* Residues */ + rv_bits(iw, 6,&residue_count_less1); + wv_bits(ow, 6, residue_count_less1); + residue_count = residue_count_less1 + 1; + + for (i = 0; i < residue_count; i++) { + uint32_t residue_type = 0, residue_classifications = 0; + uint32_t residue_begin = 0, residue_end = 0, residue_partition_size_less1 = 0, residue_classifications_less1 = 0, residue_classbook = 0; + uint32_t residue_cascade[64+1]; /* 6b +1 */ + + rv_bits(iw, 2,&residue_type); + wv_bits(ow, 16, residue_type); /* 2b to 16b */ + + if (residue_type > 2) { + VGM_LOG("Wwise Vorbis: invalid residue type\n"); + goto fail; + } + + rv_bits(iw, 24,&residue_begin); + wv_bits(ow, 24, residue_begin); + rv_bits(iw, 24,&residue_end); + wv_bits(ow, 24, residue_end); + rv_bits(iw, 24,&residue_partition_size_less1); + wv_bits(ow, 24, residue_partition_size_less1); + rv_bits(iw, 6,&residue_classifications_less1); + wv_bits(ow, 6, residue_classifications_less1); + rv_bits(iw, 8,&residue_classbook); + wv_bits(ow, 8, residue_classbook); + residue_classifications = residue_classifications_less1 + 1; + + if (residue_classbook >= codebook_count) { + VGM_LOG("Wwise Vorbis: invalid residue classbook\n"); + goto fail; + } + + memset(residue_cascade, 0, sizeof(uint32_t)*(64+1)); + + for (j = 0; j < residue_classifications; j++) { + uint32_t high_bits = 0, lowv_bits = 0, bitflag = 0; + + high_bits = 0; + + rv_bits(iw, 3,&lowv_bits); + wv_bits(ow, 3, lowv_bits); + + rv_bits(iw, 1,&bitflag); + wv_bits(ow, 1, bitflag); + if (bitflag) { + rv_bits(iw, 5,&high_bits); + wv_bits(ow, 5, high_bits); + } + + residue_cascade[j] = high_bits * 8 + lowv_bits; + } + + for (j = 0; j < residue_classifications; j++) { + for (k = 0; k < 8; k++) { + if (residue_cascade[j] & (1 << k)) { + uint32_t residue_book = 0; + + rv_bits(iw, 8,&residue_book); + wv_bits(ow, 8, residue_book); + + if (residue_book >= codebook_count) { + VGM_LOG("Wwise Vorbis: invalid residue book\n"); + goto fail; + } + } + } + } + } + + + /* Mappings */ + rv_bits(iw, 6,&mapping_count_less1); + wv_bits(ow, 6, mapping_count_less1); + mapping_count = mapping_count_less1 + 1; + + for (i = 0; i < mapping_count; i++) { + uint32_t mapping_type = 0, submaps_flag = 0, submaps = 0, square_polar_flag = 0; + uint32_t mapping_reserved = 0; + + // always mapping type 0, the only one + mapping_type = 0; + wv_bits(ow, 16, mapping_type); + + rv_bits(iw, 1,&submaps_flag); + wv_bits(ow, 1, submaps_flag); + + submaps = 1; + if (submaps_flag) { + uint32_t submaps_less1 = 0; + + rv_bits(iw, 4,&submaps_less1); + wv_bits(ow, 4, submaps_less1); + submaps = submaps_less1 + 1; + } + + rv_bits(iw, 1,&square_polar_flag); + wv_bits(ow, 1, square_polar_flag); + + if (square_polar_flag) { + uint32_t coupling_steps_less1 = 0, coupling_steps = 0; + + rv_bits(iw, 8,&coupling_steps_less1); + wv_bits(ow, 8, coupling_steps_less1); + coupling_steps = coupling_steps_less1 + 1; + + for (j = 0; j < coupling_steps; j++) { + uint32_t magnitude = 0, angle = 0; + int magnitude_bits = ww2ogg_tremor_ilog(channels-1); + int angle_bits = ww2ogg_tremor_ilog(channels-1); + + rv_bits(iw, magnitude_bits,&magnitude); + wv_bits(ow, magnitude_bits, magnitude); + rv_bits(iw, angle_bits,&angle); + wv_bits(ow, angle_bits, angle); + + if (angle == magnitude || magnitude >= channels || angle >= channels) { + VGM_LOG("Wwise Vorbis: invalid coupling (angle=%i, mag=%i, ch=%i)\n", angle, magnitude,channels); + goto fail; + } + } + } + + // a rare reserved field not removed by Ak! + rv_bits(iw, 2,&mapping_reserved); + wv_bits(ow, 2, mapping_reserved); + if (0 != mapping_reserved) { + VGM_LOG("Wwise Vorbis: mapping reserved field nonzero\n"); + goto fail; + } + + if (submaps > 1) { + for (j = 0; j < channels; j++) { + uint32_t mapping_mux = 0; + + rv_bits(iw, 4,&mapping_mux); + wv_bits(ow, 4, mapping_mux); + if (mapping_mux >= submaps) { + VGM_LOG("Wwise Vorbis: mapping_mux >= submaps\n"); + goto fail; + } + } + } + + for (j = 0; j < submaps; j++) { + uint32_t time_config = 0, floor_number = 0, residue_number = 0; + + // Another! Unused time domain transform configuration placeholder! + rv_bits(iw, 8,&time_config); + wv_bits(ow, 8, time_config); + + rv_bits(iw, 8,&floor_number); + wv_bits(ow, 8, floor_number); + if (floor_number >= floor_count) { + VGM_LOG("Wwise Vorbis: invalid floor mapping\n"); + goto fail; + } + + rv_bits(iw, 8,&residue_number); + wv_bits(ow, 8, residue_number); + if (residue_number >= residue_count) { + VGM_LOG("Wwise Vorbis: invalid residue mapping\n"); + goto fail; + } + } + } + + + /* Modes */ + rv_bits(iw, 6,&mode_count_less1); + wv_bits(ow, 6, mode_count_less1); + mode_count = mode_count_less1 + 1; + + memset(data->mode_blockflag, 0, sizeof(uint8_t)*(64+1)); /* up to max mode_count */ + data->mode_bits = ww2ogg_tremor_ilog(mode_count-1); /* for mod_packets */ + + for (i = 0; i < mode_count; i++) { + uint32_t block_flag = 0, windowtype = 0, transformtype = 0, mapping = 0; + + rv_bits(iw, 1,&block_flag); + wv_bits(ow, 1, block_flag); + + data->mode_blockflag[i] = (block_flag != 0); /* for mod_packets */ + + windowtype = 0; + transformtype = 0; + wv_bits(ow, 16, windowtype); + wv_bits(ow, 16, transformtype); + + rv_bits(iw, 8,&mapping); + wv_bits(ow, 8, mapping); + if (mapping >= mapping_count) { + VGM_LOG("Wwise Vorbis: invalid mode mapping\n"); + goto fail; + } + } + } + + + /* end flag */ + { + uint32_t framing = 0; + + framing = 1; + wv_bits(ow, 1, framing); + } + + /* remove trailing garbage bits */ + if (ow->b_off % 8 != 0) { + uint32_t padding = 0; + int padding_bits = 8 - (ow->b_off % 8); + + wv_bits(ow, padding_bits, padding); + } + + + return 1; +fail: + return 0; +} + + +/* **************************************************************************** */ +/* INTERNAL UTILS */ +/* **************************************************************************** */ + +/* loads an external Wwise Vorbis Codebooks file (wvc) referenced by ID and returns size */ +static int load_wvc(uint8_t* ibuf, size_t ibufsize, uint32_t codebook_id, wwise_setup_t setup_type, STREAMFILE* sf) { + size_t bytes; + + /* try to locate from the precompiled list */ + bytes = load_wvc_array(ibuf, ibufsize, codebook_id, setup_type); + if (bytes) + return bytes; + + /* try to load from external file (ignoring type, just use file if found) */ + bytes = load_wvc_file(ibuf, ibufsize, codebook_id, sf); + if (bytes) + return bytes; + + /* not found */ + VGM_LOG("Wwise Vorbis: codebook_id %04x not found\n", codebook_id); + return 0; +} + +static int load_wvc_file(uint8_t* buf, size_t bufsize, uint32_t codebook_id, STREAMFILE* sf) { + STREAMFILE* sfWvc = NULL; + size_t wvc_size = 0; + + { + char setupname[PATH_LIMIT]; + char pathname[PATH_LIMIT]; + char *path; + + /* read "(dir/).wvc" */ + sf->get_name(sf,pathname,sizeof(pathname)); + path = strrchr(pathname,DIR_SEPARATOR); + if (path) + *(path+1) = '\0'; + else + pathname[0] = '\0'; + + snprintf(setupname,PATH_LIMIT,"%s.wvc", pathname); + sfWvc = sf->open(sf,setupname,STREAMFILE_DEFAULT_BUFFER_SIZE); + if (!sfWvc) goto fail; + + wvc_size = sfWvc->get_size(sfWvc); + } + + /* find codebook and copy to buffer */ + { + off_t table_start, codebook_offset; + size_t codebook_size; + int codebook_count; + + /* at the end of the WVC is an offset table, and we need to find codebook id (number) offset */ + table_start = read_u32le(wvc_size - 4, sfWvc); /* last offset */ + codebook_count = ((wvc_size - table_start) / 4) - 1; + if (table_start > wvc_size || codebook_id >= codebook_count) goto fail; + + codebook_offset = read_u32le(table_start + codebook_id*4, sfWvc); + codebook_size = read_u32le(table_start + codebook_id*4 + 4, sfWvc) - codebook_offset; + if (codebook_size > bufsize) goto fail; + + if (read_streamfile(buf, codebook_offset, codebook_size, sfWvc) != codebook_size) + goto fail; + sfWvc->close(sfWvc); + + return codebook_size; + } + + +fail: + if (sfWvc) sfWvc->close(sfWvc); + return 0; +} + +static int load_wvc_array(uint8_t* buf, size_t bufsize, uint32_t codebook_id, wwise_setup_t setup_type) { +#if WWISE_VORBIS_USE_PRECOMPILED_WVC + + /* get pointer to array */ + { + int i, list_length; + const wvc_info * wvc_list; + + switch (setup_type) { + case WWV_EXTERNAL_CODEBOOKS: + wvc_list = wvc_list_standard; + list_length = sizeof(wvc_list_standard) / sizeof(wvc_info); + break; + case WWV_AOTUV603_CODEBOOKS: + wvc_list = wvc_list_aotuv603; + list_length = sizeof(wvc_list_standard) / sizeof(wvc_info); + break; + default: + goto fail; + } + + for (i=0; i < list_length; i++) { + if (wvc_list[i].id == codebook_id) { + if (wvc_list[i].size > bufsize) goto fail; + /* found: copy data as-is */ + memcpy(buf,wvc_list[i].codebook, wvc_list[i].size); + return wvc_list[i].size; + } + } + } + + // this can be used if the lists contained a 1:1 dump of the codebook files +#if 0 + if (wvc == NULL) goto fail; + /* find codebook and copy to buffer */ + { + off_t table_start, codebook_offset; + size_t codebook_size; + int codebook_count; + + /* at the end of the WVC is an offset table, and we need to find codebook id (number) offset */ + table_start = get_32bitLE(wvc + wvc_size - 4); /* last offset */ + codebook_count = ((wvc_size - table_start) / 4) - 1; + if (codebook_id >= codebook_count) goto fail; + + codebook_offset = get_32bitLE(wvc + table_start + codebook_id*4); + codebook_size = get_32bitLE(wvc + table_start + codebook_id*4 + 4) - codebook_offset; + if (codebook_size > bufsize) goto fail; + + memcpy(buf, wvc+codebook_offset, codebook_size); + + return codebook_size; + } +#endif + +fail: +#endif + return 0; +} + +#endif diff --git a/src/decode.c b/src/decode.c index a5603187..f252831d 100644 --- a/src/decode.c +++ b/src/decode.c @@ -1,6 +1,6 @@ #include "vgmstream.h" #include "decode.h" -//#include "layout/layout.h" +#include "layout/layout.h" #include "coding/coding.h" #include "mixing.h" #include "plugins.h" @@ -1423,7 +1423,7 @@ int vgmstream_do_loop(VGMSTREAM* vgmstream) { } } - /* prepare certain codecs' internal state for looping */ + /* loop codecs */ seek_codec(vgmstream); /* restore! */ @@ -1436,6 +1436,18 @@ int vgmstream_do_loop(VGMSTREAM* vgmstream) { vgmstream->next_block_offset = vgmstream->loop_next_block_offset; //vgmstream->pstate = vgmstream->lstate; /* play state is applied over loops */ + /* loop layouts (after restore, in case layout needs state manipulations) */ + switch(vgmstream->layout_type) { + case layout_segmented: + loop_layout_segmented(vgmstream, vgmstream->loop_current_sample); + break; + case layout_layered: + loop_layout_layered(vgmstream, vgmstream->loop_current_sample); + break; + default: + break; + } + return 1; /* looped */ } diff --git a/src/layout/Makefile.autotools.am b/src/layout/Makefile.autotools.am index 628b5705..1199040d 100644 --- a/src/layout/Makefile.autotools.am +++ b/src/layout/Makefile.autotools.am @@ -17,6 +17,11 @@ if HAVE_VORBISFILE AM_CFLAGS += -DVGM_USE_VORBIS endif endif + if HAVE_LIBMPG123 AM_CFLAGS += -DVGM_USE_MPEG endif + +if HAVE_FFMPEG +AM_CFLAGS += -DVGM_USE_FFMPEG +endif diff --git a/src/layout/layered.c b/src/layout/layered.c index ac5e3b87..2d282e7f 100644 --- a/src/layout/layered.c +++ b/src/layout/layered.c @@ -9,54 +9,34 @@ /* Decodes samples for layered streams. - * Similar to interleave layout, but decodec samples are mixed from complete vgmstreams, each - * with custom codecs and different number of channels, creating a single super-vgmstream. + * Similar to flat layout, but decoded vgmstream are mixed into a final buffer, each vgmstream + * may have different codecs and number of channels, creating a single super-vgmstream. * Usually combined with custom streamfiles to handle data interleaved in weird ways. */ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { - int samples_written = 0, loop_samples_skip = 0; + int samples_written = 0; layered_layout_data* data = vgmstream->layout_data; + int samples_per_frame, samples_this_block; + samples_per_frame = VGMSTREAM_LAYER_SAMPLE_BUFFER; + samples_this_block = vgmstream->num_samples; /* do all samples if possible */ while (samples_written < sample_count) { int samples_to_do; - int samples_this_block = VGMSTREAM_LAYER_SAMPLE_BUFFER; - int layer, ch = 0; + int layer, ch; - if (data->external_looping) { - /* normally each layer handles its own looping internally, except when using config - * were each layer is treated as a solid part, so loop is applied externally */ - - if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) { - for (layer = 0; layer < data->layer_count; layer++) { - reset_vgmstream(data->layers[layer]); - //todo per-layer seeking instead of layout looping - } - - loop_samples_skip = vgmstream->loop_start_sample; - vgmstream->current_sample = 0; - vgmstream->samples_into_block = 0; - continue; - } - - samples_to_do = get_vgmstream_samples_to_do(vgmstream->num_samples, samples_this_block, vgmstream); - if (samples_to_do > sample_count - samples_written) - samples_to_do = sample_count - samples_written; - - /* looping: discard until actual start */ - if (loop_samples_skip > 0) { - if (samples_to_do > loop_samples_skip) - samples_to_do = loop_samples_skip; - } - } - else { - samples_to_do = samples_this_block; - if (samples_to_do > sample_count - samples_written) - samples_to_do = sample_count - samples_written; + if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) { + /* handle looping (loop_layout has been called below) */ + continue; } + samples_to_do = get_vgmstream_samples_to_do(samples_this_block, samples_per_frame, vgmstream); + if (samples_to_do > sample_count - samples_written) + samples_to_do = sample_count - samples_written; + /* decode all layers */ + ch = 0; for (layer = 0; layer < data->layer_count; layer++) { int s, layer_ch, layer_channels; @@ -68,10 +48,6 @@ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM* samples_to_do, data->layers[layer]); - if (loop_samples_skip > 0) { - continue; - } - /* mix layer samples to main samples */ for (layer_ch = 0; layer_ch < layer_channels; layer_ch++) { for (s = 0; s < samples_to_do; s++) { @@ -84,25 +60,40 @@ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM* } } - if (loop_samples_skip > 0) { - loop_samples_skip -= samples_to_do; - vgmstream->samples_into_block += samples_to_do; - continue; - } + + samples_written += samples_to_do; + vgmstream->current_sample += samples_to_do; + vgmstream->samples_into_block += samples_to_do; + } +} +void loop_layout_layered(VGMSTREAM* vgmstream, int32_t loop_sample) { + int layer; + layered_layout_data* data = vgmstream->layout_data; + + + for (layer = 0; layer < data->layer_count; layer++) { if (data->external_looping) { - samples_written += samples_to_do; - vgmstream->current_sample += samples_to_do; - vgmstream->samples_into_block += samples_to_do; + /* looping is applied over resulting decode, as each layer is its own "solid" block with + * config and needs 'external' seeking */ + seek_vgmstream(data->layers[layer], loop_sample); } else { - samples_written += samples_to_do; - vgmstream->current_sample = data->layers[0]->current_sample; - vgmstream->samples_into_block = 0; /* handled in each layer */ - vgmstream->loop_count = data->layers[0]->loop_count; + /* looping is aplied as internal loops. normally each layer does it automatically, but + * just calls do_loop manually to behave a bit more controlled, and so that manual + * calls to do_loop work (used in seek_vgmstream) */ + if (data->layers[layer]->loop_flag) { /* mixing looping and non-looping layers is allowed */ + data->layers[layer]->current_sample = data->layers[layer]->loop_end_sample; /* forces do loop */ + vgmstream_do_loop(data->layers[layer]); /* guaranteed to work should loop_layout be called */ + } } } + + /* could always call seek_vgmstream, but it's not optimized to loop non-config vgmstreams ATM */ + + vgmstream->current_sample = loop_sample; + vgmstream->samples_into_block = loop_sample; } diff --git a/src/layout/layout.h b/src/layout/layout.h index 86cc3460..2b6405dc 100644 --- a/src/layout/layout.h +++ b/src/layout/layout.h @@ -59,6 +59,7 @@ segmented_layout_data* init_layout_segmented(int segment_count); int setup_layout_segmented(segmented_layout_data* data); void free_layout_segmented(segmented_layout_data* data); void reset_layout_segmented(segmented_layout_data* data); +void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample); VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment); void render_vgmstream_layered(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream); @@ -66,6 +67,7 @@ layered_layout_data* init_layout_layered(int layer_count); int setup_layout_layered(layered_layout_data* data); void free_layout_layered(layered_layout_data* data); void reset_layout_layered(layered_layout_data* data); +void loop_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample); VGMSTREAM *allocate_layered_vgmstream(layered_layout_data* data); #endif diff --git a/src/layout/segmented.c b/src/layout/segmented.c index 2e7fe3a8..38ab5150 100644 --- a/src/layout/segmented.c +++ b/src/layout/segmented.c @@ -12,11 +12,10 @@ * Chains together sequential vgmstreams, for data divided into separate sections or files * (like one part for intro and other for loop segments, which may even use different codecs). */ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { - int samples_written = 0, loop_samples_skip = 0; + int samples_written = 0, samples_this_block; segmented_layout_data* data = vgmstream->layout_data; int use_internal_buffer = 0; - /* normally uses outbuf directly (faster?) but could need internal buffer if downmixing */ if (vgmstream->channels != data->input_channels) { use_internal_buffer = 1; @@ -27,81 +26,49 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA return; } + samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]); + + //VGM_LOG("segment decode start: cur=%i, this=%i, into=%i\n", data->current_segment, samples_this_block, vgmstream->samples_into_block); while (samples_written < sample_count) { int samples_to_do; - int samples_this_segment = vgmstream_get_samples(data->segments[data->current_segment]); if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) { - int segment, loop_segment, total_samples; + /* handle looping (loop_layout has been called below, changes segments/state) */ + samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]); + continue; + } - /* handle looping by finding loop segment and loop_start inside that segment */ - loop_segment = 0; - total_samples = 0; - while (total_samples < vgmstream->num_samples) { - int32_t segment_samples = vgmstream_get_samples(data->segments[loop_segment]); + /* detect segment change and restart (after loop, but before decode, to allow looping to kick in) */ + if (vgmstream->samples_into_block == samples_this_block) { + data->current_segment++; - if (vgmstream->loop_current_sample >= total_samples && - vgmstream->loop_current_sample < total_samples + segment_samples) { - loop_samples_skip = vgmstream->loop_current_sample - total_samples; - break; /* loop_start falls within loop_segment's samples */ - } - total_samples += segment_samples; - loop_segment++; + /* could happen on last segment trying to decode more samples */ + if (data->current_segment >= data->segment_count) { + VGM_LOG("SEGMENTED: wrong next segment\n"); + break; } - if (loop_segment == data->segment_count) { - VGM_LOG("SEGMENTED: can't find loop segment\n"); - loop_segment = 0; - } - - data->current_segment = loop_segment; - - /* loops can span multiple segments */ - for (segment = loop_segment; segment < data->segment_count; segment++) { - reset_vgmstream(data->segments[segment]); - } + /* in case of looping spanning multiple segments */ + reset_vgmstream(data->segments[data->current_segment]); + samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]); vgmstream->samples_into_block = 0; continue; } - samples_to_do = get_vgmstream_samples_to_do(samples_this_segment, sample_count, vgmstream); + + samples_to_do = get_vgmstream_samples_to_do(samples_this_block, sample_count, vgmstream); if (samples_to_do > sample_count - samples_written) samples_to_do = sample_count - samples_written; if (samples_to_do > VGMSTREAM_SEGMENT_SAMPLE_BUFFER /*&& use_internal_buffer*/) /* always for fade/etc mixes */ samples_to_do = VGMSTREAM_SEGMENT_SAMPLE_BUFFER; - /* looping: discard until actual start */ - if (loop_samples_skip > 0) { - if (samples_to_do > loop_samples_skip) - samples_to_do = loop_samples_skip; - } - - /* detect segment change and restart */ - if (samples_to_do == 0) { - data->current_segment++; - /* could happen on last segment trying to decode more samples */ - if (data->current_segment >= data->segment_count) { - break; - } - reset_vgmstream(data->segments[data->current_segment]); - vgmstream->samples_into_block = 0; - continue; - } - render_vgmstream( use_internal_buffer ? - data->buffer : - &outbuf[samples_written * data->output_channels], + data->buffer : &outbuf[samples_written * data->output_channels], samples_to_do, data->segments[data->current_segment]); - if (loop_samples_skip > 0) { - loop_samples_skip -= samples_to_do; - vgmstream->samples_into_block += samples_to_do; - continue; - } - if (use_internal_buffer) { int s; for (s = 0; s < samples_to_do * data->output_channels; s++) { @@ -115,6 +82,33 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA } } +void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) { + int segment, total_samples; + segmented_layout_data* data = vgmstream->layout_data; + + segment = 0; + total_samples = 0; + while (total_samples < vgmstream->num_samples) { + int32_t segment_samples = vgmstream_get_samples(data->segments[segment]); + + /* find if loop falls within segment's samples */ + if (loop_sample >= total_samples && loop_sample < total_samples + segment_samples) { + int32_t loop_relative = loop_sample - total_samples; + + seek_vgmstream(data->segments[segment], loop_relative); + data->current_segment = segment; + vgmstream->samples_into_block = loop_relative; + break; + } + total_samples += segment_samples; + segment++; + } + + if (segment == data->segment_count) { + VGM_LOG("SEGMENTED: can't find loop segment\n"); + } +} + segmented_layout_data* init_layout_segmented(int segment_count) { segmented_layout_data* data = NULL; diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 80f1c17e..3dba4986 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -2002,6 +2002,10 @@ RelativePath=".\coding\g72x_state.h" > + + @@ -2010,6 +2014,10 @@ RelativePath=".\coding\nwa_decoder.h" > + + diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index b7a85896..c18e5f4c 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -84,7 +84,9 @@ + + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index c97a6270..90608578 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -242,9 +242,15 @@ ext_libs\Header Files + + coding\Header Files + coding\Header Files + + coding\Header Files + coding\Header Files diff --git a/src/meta/Makefile.autotools.am b/src/meta/Makefile.autotools.am index c313c3a2..407ea731 100644 --- a/src/meta/Makefile.autotools.am +++ b/src/meta/Makefile.autotools.am @@ -17,6 +17,11 @@ if HAVE_VORBISFILE AM_CFLAGS += -DVGM_USE_VORBIS endif endif + if HAVE_LIBMPG123 AM_CFLAGS += -DVGM_USE_MPEG endif + +if HAVE_FFMPEG +AM_CFLAGS += -DVGM_USE_FFMPEG +endif diff --git a/src/plugins.c b/src/plugins.c index a1b55c80..7bec001f 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -129,11 +129,11 @@ static void load_player_config(play_config_t* def, vgmstream_cfg_t* vcfg) { def->really_force_loop = vcfg->really_force_loop; def->ignore_fade = vcfg->ignore_fade; - def->loop_count = vcfg->loop_times; //todo loop times + def->loop_count = vcfg->loop_count; def->loop_count_set = 1; def->fade_delay = vcfg->fade_delay; def->fade_delay_set = 1; - def->fade_time = vcfg->fade_period; //todo loop period + def->fade_time = vcfg->fade_time; def->fade_time_set = 1; } diff --git a/src/plugins.h b/src/plugins.h index db1ab55c..8b26cdf3 100644 --- a/src/plugins.h +++ b/src/plugins.h @@ -53,9 +53,9 @@ typedef struct { int ignore_fade; /* don't fade after N loops */ /* song processing */ - double loop_times; /* target loops */ + double loop_count; /* target loops */ double fade_delay; /* fade delay after target loops */ - double fade_period; /* fade time after target loops */ + double fade_time; /* fade period after target loops */ //int downmix; /* max number of channels allowed (0=disable downmix) */ diff --git a/src/render.c b/src/render.c index 01314ffb..72a76130 100644 --- a/src/render.c +++ b/src/render.c @@ -513,14 +513,24 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) { int is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */ + /* will decode and loop until seek sample, but slower */ + //todo apply same loop logic as below, or pretend we have play_forever + settings? if (!vgmstream->config_enabled) { - //todo same but ignore play duration or play_position + //;VGM_LOG("SEEK: simple seek=%i, cur=%i\n", seek_sample, vgmstream->current_sample); + if (seek_sample < vgmstream->current_sample) { + decode_samples = seek_sample; + reset_vgmstream(vgmstream); + } + else { + decode_samples = seek_sample - vgmstream->current_sample; + } + + seek_force_decode(vgmstream, decode_samples); return; } - //todo optimize layout looping with seek_vgmstream //todo could improve performance bit if hit_loop wasn't lost when calling reset - //todo wrong seek with ignore fade + //todo wrong seek with ignore fade, also for layered layers (pass count to force loop + layers) /* seeking to requested sample normally means decoding and discarding up to that point (from diff --git a/winamp/in_vgmstream.c b/winamp/in_vgmstream.c index 323e83f9..a20bf5ba 100644 --- a/winamp/in_vgmstream.c +++ b/winamp/in_vgmstream.c @@ -936,8 +936,8 @@ static void apply_config(VGMSTREAM* vgmstream, winamp_settings_t* settings) { vcfg.allow_play_forever = 1; vcfg.play_forever = settings->loop_forever; - vcfg.loop_times = settings->loop_count; - vcfg.fade_period = settings->fade_time; + vcfg.loop_count = settings->loop_count; + vcfg.fade_time = settings->fade_time; vcfg.fade_delay = settings->fade_delay; vcfg.ignore_loop = settings->ignore_loop; diff --git a/xmplay/xmp_vgmstream.c b/xmplay/xmp_vgmstream.c index 22ca6393..15cc95d5 100644 --- a/xmplay/xmp_vgmstream.c +++ b/xmplay/xmp_vgmstream.c @@ -173,8 +173,8 @@ static void apply_config(VGMSTREAM* vgmstream) { vcfg.allow_play_forever = 0; //vcfg.play_forever = loop_forever; - vcfg.loop_times = loop_count; - vcfg.fade_period = fade_seconds; + vcfg.loop_count = loop_count; + vcfg.fade_time = fade_seconds; vcfg.fade_delay = fade_delay; vcfg.ignore_loop = ignore_loop;