From cf7ef5067a34ff971bf5acb90ff62faee1ca146d Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 2 Feb 2020 18:42:00 +0100 Subject: [PATCH 01/11] CRLF to LF --- doc/TXTH.md | 1682 +++++++++++++++++++++---------------------- src/meta/ogg_opus.c | 348 ++++----- src/meta/opus.c | 878 +++++++++++----------- 3 files changed, 1454 insertions(+), 1454 deletions(-) diff --git a/doc/TXTH.md b/doc/TXTH.md index 61b21b24..ef4bbfda 100644 --- a/doc/TXTH.md +++ b/doc/TXTH.md @@ -1,841 +1,841 @@ -# TXTH FORMAT - -TXTH is a simple text file that uses text commands to simulate a header for files unsupported by vgmstream, mainly headerless audio. - -When an unsupported file is loaded (for instance "bgm01.snd"), vgmstream tries to find a TXTH header in the same dir, in this order: -- `(filename.ext).txth` -- `.(ext).txth` -- `.txth` - -If found and parsed correctly (the .txth may be rejected if incorrect commands are found) vgmstream will try to play the file as described. Extension must be accepted/added to vgmstream (plugins like foobar2000 only load extensions from a whitelist in formats.c), or one could rename to any supported extension (like .vgmstream), or leave the file extensionless. - -You can also use `.(sub).(ext).txth` (if the file is `filename.sub.ext`), to allow mixing slightly different files in the same folder. The `sub` part doesn't need to be an extension, for example: -- `001.1ch.str`, `001.1ch.str` may use `.1ch.txth` -- `003.2ch.str`, `003.2ch.str` may use `.2ch.txth` -- etc - -## Example of a TXTH file -For an unsupported `bgm01.vag` this would be a simple TXTH for it: -``` -codec = PSX #data uses PS-ADPCM -sample_rate = @0x10$2 #get sample rate at offset 0x10, 16 bit value -channels = @0x14 #get number of channels at offset 14 -interleave = 0x1000 #fixed value -start_offset = 0x100 #data starts after exactly this value -num_samples = data_size #find automatically number of samples in the file -loop_flag = auto #find loop points in PS-ADPCM -``` -A text file with the above commands must be saved as `.vag.txth` or `.txth` (preferably the former), notice it starts with a "." (dot). On Windows files starting with a dot can be created by appending a dot at the end when renaming: `.txth.` - -While the main point is playing the file, many of TXTH's features are aimed towards keeping original data intact, for documentation and preservation purposes; try leaving data as untouched as possible and consider how the game plays the file, as there is a good chance some feature can mimic it. - - -## Available commands - -The file is made of lines with `key = value` commands describing a header. Commands are all case sensitive and spaces are optional: `key=value`, `key = value`, and so on are all ok. Comments start with # and can be inlined. - -The parser is fairly simple and may be buggy or unexpected in some cases. The order of keys is variable but some things won't work if others aren't defined (ex. bytes-to-samples may not work without channels or interleave) or need to be done in a certain order (due to technical reasons) as explained below. - -To get a file playing you need to correctly set, at least: `codec` and sometimes `interleave`, `sample_rate`, `channels` and `num_samples`, or use the "subfile" feature. - -### VALUES -The following can be used in place of `(value)` for `(key) = (value)` commands. -- `(number)`: constant number in dec/hex, unsigned (no +10 or -10). - * Examples: `44100, 40, 0x40 (decimal=64)` -- `(offset)`: read a value at offset inside the file, format being `@(number)[:LE|BE][$1|2|3|4]` - * `@(number)`: offset of the value (required) - * if `base_offset` is defined this value is modified (see later) - * `:LE|BE`: value is little/big endian (optional, defaults to LE) - * `$1|2|3|4`: value has size of 8/16/24/32 bit (optional, defaults to 4) - * Example: `@0x10:BE$2` means `get big endian 16b value at 0x10` -- `(field)`: uses current value of some fields. Accepted strings: - - `interleave, interleave_last, channels, sample_rate, start_offset, data_size, num_samples, loop_start_sample, loop_end_sample, subsong_count, subsong_offset, subfile_offset, subfile_size, name_valueX` -- `(other)`: other special values for certain keys, described per key - - -The above may be combined with math operations (+-*/&): `(key) = (number) (op) (offset) (op) (field) (...)` - -### KEYS - -#### CODEC [REQUIRED] -Sets codec used to encode the data. Some codecs need interleave or other config -as explained below, but often will use default values. Accepted codec strings: -``` -# - PSX PlayStation ADPCM -# * For many PS1/PS2/PS3 games -# * Interleave is multiple of 0x10 (default), often +0x1000 -# - PSX_bf PlayStation ADPCM with bad flags -# * Variation with garbage data, for rare PS2 games -# - XBOX Xbox IMA ADPCM (mono/stereo) -# * For many XBOX games, and some PC games -# * Special interleave is multiple of 0x24 (mono) or 0x48 (stereo) -# - DSP|NGC_DSP Nintendo GameCube ADPCM -# * For many GC/Wii/3DS games -# * Interleave is multiple of 0x08 (default), often +0x1000 -# * Must set decoding coefficients (coef_offset/spacing/etc) -# * Should set ADPCM state (hist_offset/spacing/etc) -# - DTK|NGC_DTK Nintendo ADP/DTK ADPCM -# * For rare GC games -# - PCM16LE PCM 16-bit little endian -# * For many games (usually on PC) -# * Interleave is multiple of 0x2 (default) -# - PCM16BE PCM 16-bit big endian -# * Variation for certain consoles (GC/Wii/PS3/X360/etc) -# - PCM8 PCM 8-bit signed -# * For some games (usually on PC) -# * Interleave is multiple of 0x1 (default) -# - PCM8_U PCM 8-bit unsigned -# * Variation with modified encoding -# - PCM8_U_int PCM 8-bit unsigned (interleave block) -# * Variation with modified encoding -# - IMA IMA ADPCM (mono/stereo) -# * For some PC games, and rarely consoles -# * Special interleave is multiple of 0x1, often +0x80 -# - DVI_IMA IMA ADPCM (DVI order) -# * Variation with modified encoding -# - AICA Yamaha AICA ADPCM (mono/stereo) -# * For some Dreamcast games, and some arcade (Naomi) games -# * Special interleave is multiple of 0x1 -# - APPLE_IMA4 Apple Quicktime IMA ADPCM -# * For some Mac/iOS games -# - MS_IMA Microsoft IMA ADPCM -# * For some PC games -# * Interleave (frame size) varies, often multiple of 0x100 [required] -# - MSADPCM Microsoft ADPCM (mono/stereo) -# * For some PC games -# * Interleave (frame size) varies, often multiple of 0x100 [required] -# - SDX2 Squareroot-delta-exact 8-bit DPCM -# * For many 3DO games -# - MPEG MPEG Audio Layer file (MP1/2/3) -# * For some games (usually PC/PS3) -# * May set skip_samples -# - ATRAC3 Sony ATRAC3 -# * For some PS2 and PS3 games -# * Interleave (frame size) can be 0x60/0x98/0xC0 * channels [required] -# * Should set skip_samples (more than 1024+69 but varies) -# - ATRAC3PLUS Sony ATRAC3plus -# * For many PSP games and rare PS3 games -# * Interleave (frame size) can be: [required] -# Mono: 0x0118|0178|0230|02E8 -# Stereo: 0x0118|0178|0230|02E8|03A8|0460|05D0|0748|0800 -# 6/8 channels: multiple of one of the above -# * Should set skip_samples (more than 2048+184 but varies) -# - XMA1 Microsoft XMA1 -# * For early X360 games -# - XMA2 Microsoft XMA2 -# * For later X360 games -# - FFMPEG Any headered FFmpeg format -# * For uncommon games -# * May set skip_samples -# - AC3 AC3/SPDIF -# * For few PS2 games -# * Should set skip_samples (around 256 but varies) -# - PCFX PC-FX ADPCM -# * For many PC-FX games -# * Interleave is multiple of 0x1, often +0x8000 -# * Sample rate may be ~31468/~15734/~10489/~7867 -# - PCM4 PCM 4-bit signed -# * For early consoles -# - PCM4_U PCM 4-bit unsigned -# * Variation with modified encoding -# - OKI16 OKI ADPCM with 16-bit output (not VOX/Dialogic 12-bit) -# * For few PS2 games (Sweet Legacy, Hooligan) -# - AAC Advanced Audio Coding (raw without .mp4) -# * For some 3DS games and many iOS games -# * Should set skip_samples (around 1024 but varies) -codec = (codec string) -``` - -#### CODEC VARIATIONS -Changes the behavior of some codecs: -``` -# - NGC_DSP: 0=normal interleave, 1=byte interleave, 2=no interleave -# - XMA1|XMA2: 0=dual multichannel (2ch xN), 1=single multichannel (1ch xN) -# - XBOX: 0=standard (mono or stereo interleave), 1=force mono interleave mode -# - PCFX: 0=standard, 1='buggy encoder' mode, 2/3=same as 0/1 but with double volume -# - PCM4|PCM4_U: 0=low nibble first, 1=high nibble first -# - others: ignored -codec_mode = (variation) -``` - -#### (deprecated) VALUE MODIFIERS -*Use inline math instead of this.* - -Changes next read to: `(key) = (value) */+- value_(op)`. Set to 0 when done using, as it affects ANY value. Priority is as listed. -``` -value_mul|value_* = (value) -value_div|value_/ = (value) -value_add|value_+ = (value) -value_sub|value_- = (value) -``` - -#### INTERLEAVE / FRAME SIZE [REQUIRED depending on codec] -This value changes how data is read depending on the codec: -- For mono/interleaved codecs it's the amount of data between channels, and while optional (defaults described in the "codec" section) you'll often need to set it to get proper sound. -- For codecs with custom frame sizes (MSADPCM, MS-IMA, ATRAC3/plus) means frame size and is required. -- Interleave 0 means "stereo mode" for codecs marked as "mono/stereo", and setting it will usually force mono-interleaved mode. - -Special values: -- `half_size`: sets interleave as data_size / channels automatically -``` -interleave = (value)|half_size -``` - -#### INTERLEAVE IN THE LAST BLOCK -In some files with interleaved data the last block (`interleave * channels`) of data is smaller than normal, so `interleave` is smaller for that block. Setting this fixes decoding glitches at the end. - -Note that this doesn't affect files with padding data in the last block (as the `interleave` itself is constant). - -Special values: -- `auto`: calculate based on channels, interleave and data_size/start_offset -``` -interleave_last = (value)|auto -``` - -#### ID VALUES -Validates that `id_value` (normally set as constant value) matches value read at `id_offset`. The file will be rejected and won't play if values don't match. - -Can be redefined several times, it's checked whenever a new id_offset is found. -``` -id_value = (value) -id_offset = (value) -``` - -#### NUMBER OF CHANNELS [REQUIRED] -``` -channels = (value) -``` - -#### MUSIC FREQUENCY [REQUIRED] -``` -sample_rate = (value) -``` - -#### DATA START -Where encoded data actually starts, after the header part. Defaults to 0. -``` -start_offset = (value) -``` - -#### DATA SIZE -Special variable that can be used in sample values. Defaults to `(file_size - start_offset)`, re-calculated when `start_offset` is set. With multiple subsongs, `block_size` or padding are set this it's recalculated as well. - -If data_size is manually set it stays constant and won't be auto changed. -``` -data_size = (value) -``` - -#### DATA PADDING -Some files have extra padding at the end that is meant to be ignored. This adjusts the padding in `data_size`, manually or auto-calculated. - -Special values (for PS-ADPCM only): -- `auto`: discards null frames -- `auto-empty`: discards null and 'empty' frames (for games with weird padding) -``` -padding_size = (value)|auto|auto-empty -``` - -#### SAMPLE MEANINGS -Modifies the meaning of sample fields when set *before* them. - -Accepted values: -- `samples`: exact sample (default) -- `bytes`: automatically converts bytes/offset to samples (applies after */+-& modifiers) -- `blocks`: same as bytes, but value is given in blocks/frames - * Value is internally converted from blocks to bytes first: `bytes = (value * interleave*channels)` - -Some codecs can't convert bytes-to-samples at the moment: `FFMPEG`. For XMA1/2, bytes does special parsing, with loop values being bit offsets within data (as XMA has a peculiar way to loop). -``` -sample_type = samples|bytes|blocks -``` - -#### SAMPLE VALUES [REQUIRED (num_samples)] -Special values: -- `data_size`: automatically converts bytes-to-samples -``` -num_samples = (value)|data_size -loop_start_sample = (value) -loop_end_sample = (value)|data_size -``` - -#### LOOP SETTING -Force loop on or off, as loop start/end may be defined but not used. If not set, by default it loops when loop_end_sample is defined and less than num_samples. - -Special values: -- auto: tries to autodetect loop points for PS-ADPCM data using data loop flags. - -Sometimes games give loop flags different meaning, so behavior can be tweaked by defining `loop_behavior` before `loop_flag`: -- `default`: values 0 or 0xFFFF/0xFFFFFFFF (-1) disable looping, but not 0xFF (loop endlessly) -- `negative`: values 0xFF/0xFFFF/0xFFFFFFFF (-1) enable looping -- `positive`: values 0xFF/0xFFFF/0xFFFFFFFF (-1) disable looping - -``` -loop_negative = default|negative|positive -loop_flag = (value)|auto -``` - -#### LOOP START/END MODIFIER -For XMA1/2 + sample_type=bytes it means loop subregion, if read after loop values. - -For other codecs its added to loop start/end, if read before loop values (a format may rarely have rough loop offset/bytes, then a loop adjust in samples). - -``` -loop_adjust = (value) -``` - -#### ENCODER DELAY -Beginning samples to skip, a.k.a. priming samples or encoder delay, that some codecs use to "warm up" the decoder. This is needed for proper gapless support. - -Supported codecs: `ATRAC3/ATRAC3PLUS/XMA/FFMPEG/AC3/AAC` -``` -skip_samples = (value) -``` - -#### DSP DECODING COEFFICIENTS [REQUIRED for DSP] -DSP needs a "coefs" list to decode correctly. These are 8*2 16-bit values per channel, starting from `coef_offset`. - -Usually each channel uses its own list, so we may need to set separation per channel, usually 0x20 (16 values * 2 bytes). So channel N coefs are read at `coef_offset + coef_spacing * N` - -Those 16-bit coefs can be little or big endian (usually BE), set `coef_endianness` directly or in an offset value where ´0=LE, >0=BE´. - -While the coef table is almost always included per-file, some games have their coef table in the executable or precalculated somehow. You can set inline coefs instead of coef_offset. Format is a long string of bytes (optionally space-separated) like `coef_table = 0x1E02DE01 3C0C0EFA ...`. You still need to set `coef_spacing` and `coef_endianness` though. -``` -coef_offset = (value) -coef_spacing = (value) -coef_endianness = BE|LE|(value) -coef_table = (string) -``` - -#### ADPCM STATE -Some ADPCM codecs need to set up their initial or "history" state, normally one or two 16-bit PCM samples per channel, starting from `hist_offset`. - -Usually each channel uses its own state, so we may need to set separation per channel. - -State values can be little or big endian (usually BE for DSP), set `hist_endianness` directly or in an offset value where ´0=LE, >0=BE´. - -Normally audio starts with silence or hist samples are set to zero and can be ignored, but it does affect a bit resulting output. - -Currently used by DSP. -``` -hist_offset = (value) -hist_spacing = (value) -hist_endianness = BE|LE|(value) -``` - -#### HEADER/BODY SETTINGS -Changes internal header/body representation to external files. - -TXTH commands are done on a "header", and decoding on "body". When loading an unsupported file it becomes the "base" file -that loads the .txth, and is both header and body. - -You can alter those, mainly for files that split header and body in separate files (load base file and txth sets header on another file). It's also possible to load the .txth directly with a set body, as a sort of "reverse TXTH" (useful with bigfiles, as you could have one .txth per song). - -Allowed values: -- (filename): open any file, subdirs also work (dir/filename) -- *.(extension): opens with same name as the "base" file (the one you open, not the .txth) plus another extension -- null: unloads file and goes back to defaults (body/header = base file). -``` -header_file = (filename)|*.(extension)|null -body_file = (filename)|*.(extension)|null -``` - -#### SUBSONGS -Sets the number of subsongs in the file, adjusting reads per subsong N: `value = @(offset) + subsong_offset*N`. number/constants values aren't adjusted though. - -Mainly for bigfiles with consecutive headers per subsong, set subsong_offset to 0 when done as it affects any reads. The current subsong number is handled externally by plugins or TXTP. -``` -subsong_count = (value) -subsong_offset = (value) -``` - -#### NAMES -Sets the name of the stream, most useful when used with subsongs. TXTH will read a string at `name_offset`, with `name_size characters`. - -`name_size` defaults to 0, which reads until null-terminator or a non-ascii character is found. - -`name_offset` can be a (number) value, but being an offset it's also adjusted by subsong_offset. -``` -name_offset = (value) -name_size = (value) -``` - -#### SUBFILES -Tells TXTH to parse a full file (ex. an Ogg) at `subfile_offset`, with size of `subfile_size` (defaults to `file size - subfile_offset` if not set). This is useful for files that are just container of other files, so you don't have to remove the extra data (since it could contain useful stuff like loop info). - -Internal subfile extension can be changed to `subfile_extension` if needed, as vgmstream won't accept unknown extensions (for example if your file uses .vgmstream or .pogg you may need to set subfile_extension = ogg). - -Setting any of those three will trigger this mode (it's ok to set offset 0). Once triggered most fields are ignored, but not all, explained later. This will also set some values like `channels` or `sample_rate` if not set for calculations/convenience. -``` -subfile_offset = (value) -subfile_size = (value) -subfile_extension = (string) -``` - -#### CHUNK DEINTERLEAVING -Some files interleave data chunks, for example 3 stereo songs pasted together, alternating 0x10000 bytes of data each. These settings allow vgmstream to play one of the chunks while ignoring the rest (read 0x10000 data, skip 0x10000*2). -File is first "dechunked" then played with using other settings (`start_offset` would point within the internal dechunked" file). It can be used to remove garbage data that affects decoding, too. - - -You need to set: -- `chunk_count`: total number of interleaved chunks (ex. 3=3 interleaved songs) -- `chunk_number`: first chunk to start (ex. 1=0x00000, 2=0x10000, 3=0x20000...) - * If you set `subsong_count` first `chunk_number` will be auto-set per subsong (subsong 1 starts from chunk number 1, subsong 2 from chunk 2, etc) -- `chunk_start`: absolute offset where chunks start (normally 0x00) -- `chunk_size`: amount of data in a single chunk (ex. 0x10000) -For fine-tuning you can optionally set (before `chunk_size`, for reasons): -- `chunk_header_size`: header to skip before chunk data (part of chunk_size) -- `chunk_data_size`: actual data size (part of chunk_size, rest is header/padding) - -So, if you set size to 0x1000, header_size 0x100, data_size is implicitly 0xF00, or if size is 0x1000 and data_size 0x800 last 0x200 is ignored padding. Use combinations of the above to make vgmstream "see" only actual codec data. -``` -chunk_count = (value) -chunk_number = (value) -chunk_start = (value) -chunk_header_size = (value) -chunk_data_size = (value) -chunk_size = (value) -``` - -#### NAME TABLE -Some games have headers for all files pasted together separate from the actual data, but this order may be hard-coded or even alphabetically ordered by filename. In those cases you can set a "name table" that assigns constant values (one or many) to filenames. This table is loaded from an external text file (for clarity) and can be set to any name, for example `name_table = .names.txt` -``` -name_table = (filename) -``` - -Inside the table you define lines mapping a filename to a bunch of values, in this format: -``` -# base definition -(filename1): (value) -... -# may put multiple comma-separated values, spaces are ok -(filenameN) : (value1), (...) , (valueN) - -# put no name before the : to set default values - : (value1), (...), (valueN) -``` -Then I'll find your current file name, and you can then reference its numbers from the list as a `name_value` field, like `base_offset = name_value`, `start_offset = 0x1000 + name_value1`, `interleave = name_value5`, etc. `(filename)` can be with or without extension (like `bgm01.vag` or just `bgm01`), and if the file's name isn't found it'll use default values, and if those aren't defined you'll get 0 instead. Being "values" they can be use math or offsets too. - -You can use wildcards to match multiple names too (it stops on first name that matches), and UTF-8 names should work, case insensitive even. -``` -bgm_??_4: 4 # 4ch: files like bgm_00_4, bgm_01_4, etc -bgm*_M: 1 # 1ch: some files end with _M for mono -bgm*: 2 # 2ch: all other files, notice order matters -``` - -While you can put anything in the numbers, this feature is meant to be used to store some number that points to the actual data inside a real multi-header, that could be set with `header_file`. If you need to store many constant values there is good chance this can be supported in some better way. - - -#### BASE OFFSET MODIFIER -You can set a default offset that affects next `@(offset)` reads making them `@(offset + base_offset)`, for cleaner parsing (particularly interesting when combined with the `name_list). - -For example instead of `channels = @0x714` you could set `base_offset = 0x710, channels = @0x04`. Set to 0 when you want to disable it. -``` -base_offset = (value) -``` - - -## Complex usages - -### Temporary values -Most commands are evaluated and calculated immediatedly, every time they are found. This is by design, as it can be used to adjust and trick for certain calculations. - -It makes TXTHs a bit harder to follow, as they are order dependant, but otherwise it's hard to accomplish some things or others become ambiguous. - - -For example, normally you are given a data_size in bytes, that can be used to calculate num_samples for all channels. -``` -channels = 2 -sample_type = bytes -num_samples = @0x10 #calculated from data_size -``` - -But sometimes this size is for a single channel only (even though the file may be stereo). You can set temporally change the channel number to force a correct calculation. -``` -channels = 1 #not the actual number of channels -sample_type = bytes -num_samples = @0x10 #calculated from channel_size -channels = 2 #change once calculations are done -``` -You can also use: -``` -channels = 2 -sample_type = bytes -num_samples = @0x10 * channels # resulting bytes is transformed to samples -``` - -Do note when using special values/strings like `data_size` in `num_samples` and `loop_end_samples` they must be alone to trigger. -``` -data_size = @0x100 -num_samples = data_size * 2 # doesn't tranform bytes-to-samples (do it before? after?) -``` -``` -data_size = @0x100 * 2 -num_samples = data_size # ok -``` -Also beware of order: -``` -start_offset = 0x200 # recalculated data_size -num_samples = data_size # transforms bytes-to-samples -data_size = @0x100 # useless as num_samples is already transformed -``` - - -### Redefining values -Some commands alter the function of all next commands and can be redefined as needed: -``` -samples_type = bytes -num_samples = @0x10 - -samples_type = sample -loop_end_sample = @0x14 -``` - -### External files -When setting external files all commands are done on the "header" file, but with some creativity you can read in multiple files. -``` -body_file = bgm01.bdy -header_file = bgm01.hdr -channels = @0x10 #base info in bgm01.hdr -header_file = bgm01.bdy -coef_offset = 0x00 #DSP coefs in bgm01.bdy -``` -Note that DSP coefs are special in that aren't read immediately, and will use *last* header_file set. - - -### Resetting values -Values may need to be reset (to 0 or other sensible value) when done. Subsong example: -``` -subsong_count = 5 -subsong_offset = 0x20 # there are 5 subsong headers, 0x20 each -channel_count = @0x10 # reads channels at 0x10+0x20*subsong -# 1st subsong: 0x10+0x20*0: 0x10 -# 2nd subsong: 0x10+0x20*1: 0x30 -# 2nd subsong: 0x10+0x20*2: 0x50 -# ... -start_offset = @0x14 # reads offset within data at 0x14+0x20*subsong - -subsong_offset = 0 # reset value -sample_rate = 0x04 # sample rate is the same for all subsongs -# Nth subsong ch: 0x04+0x00*N: 0x08 -``` - -### Math -Sometimes header values are in "sectors" or similar concepts (typical in DVD games), and need to be adjusted to a real value using some complex math: -``` -sample_type = bytes -start_offset = @0x10 * 0x800 # 0x15 * DVD sector size, for example -``` - -You can use `+-*/&` operators, and also certain fields' values: -``` -num_samples = @0x10 * channels # byte-to-samples of channel_size -``` -`data_size` is a special value for `num_samples` and `loop_end_sample` and will always convert as bytes-to-samples, though. - - -Priority is left-to-right. Do add brackets though, they are accounted for and if they are implemented in the future your .txth *will* break with impunity. -``` -# normal priority -data_size = @0x10 * 0x800 + 0x800 -# also works -data_size = (@0x10 + 1) * 0x800 -# same as above but don't do this -# (may become @0x10 + (1 * 0x800) in the future -data_size = @0x10 + 1 * 0x800 -# doesn't work at the moment, so reorder as (1 * 0x800) + @0x10 -data_size = @0x10 + (1 * 0x800) -# fails, wrong bracket count -data_size = (@0x10 + 1 * 0x800 -# fails, wrong bracket count -data_size = )@0x10 + 1 * 0x800 -``` - -If a TXTH needs too many calculations it may be better to implement directly in vgmstream though, consider reporting. - - -### Modifiers -Remnant of simpler math (priority is fixed to */+-), *shouldn't be needed anymore*. - -``` -value_multiply = 0x800 -start_offset = @0x10 -value_multiply = 0 -``` - -``` -value_add = 1 -channels = @0x08 -value_add = 0 - -value_multiply = channels -sample_type = bytes -num_samples = @0x10 -value_multiply = 0 -``` - -``` -value_add = 0x10 -value_mul = 0x800 -start_offset = @0x10 -``` - -### Subfiles -Sometimes a file is just a wrapper for another common format. In those cases you can tell TXTH to just play the internal format: -``` -subfile_offset = 0x20 # tell TXTH to parse a full file (ex. .ogg) at this offset -subfile_size = @0x10 # defaults to (file size - subfile_offset) if not set -subfile_extension = ogg # may be ommited if subfile extension is the same - -# many fields are ignored -codec = PCM16LE -interleave = 0x1000 -channels = 2 - -# a few fields are applied -sample_rate = @0x08 -num_samples = @0x10 -loop_start_sample = @0x14 -loop_end_sample = @0x18 -``` -Most fields can't be changed after parsing since doesn't make much sense technically, as the parsed subfile should supply them. You can set them to use bytes-to-samples conversions, though. -``` -# parses subfile at start with some num_samples -subfile_offset = 0x20 -# force recalculation of num_samples -codec = PSX -start_offset = 0x40 -num_samples = data_size -``` - -### Chunks -Chunks affect some values (padding size, data size, etc) and are a bit sensitive to order at the moment, due to technical complexities: -``` -# Street Fighter EX3 (PS2) - -# base config is defined normally -codec = PSX -sample_rate = 44100 -channels = 2 -interleave = 0x8000 - -# set subsong number instead of chunk_number for subsongs -subsong_count = 26 -#chunk_number = 1 -chunk_start = 0 -chunk_size = 0x10000 -chunk_count = 26 - -# after setting chunks (sizes vary when 'dechunking') -start_offset = 0x00 -padding_size = auto-empty -num_samples = data_size -``` - -Subfiles and chunks can coexist: -``` -# Gitaroo Man (PSP) - -# 3 interleaved RIFF files -subsong_count = 3 -chunk_start = 0 -chunk_size = 0x2800 -chunk_count = 3 - -# the 3 de-interleaved chunks are treated and parsed as a subsong -subfile_offset = 0 -subfile_size = @0x04 + 0x08 #RIFF size -subfile_extension = at3 -``` - -It can be used to make blocks with padding playable: -``` -# Mortal Kombat: Deception (PS2) -codec = PSX -interleave = 0x3F40 -sample_rate = 32000 -channels = 2 - -chunk_number = 1 -chunk_count = 1 -chunk_start = 0x00 -chunk_data_size = interleave * channels -chunk_size = 0x8000 - -num_samples = data_size -``` - -## Examples - -**Colin McRae DiRT (PC) .wip.txth** -``` -id_value = 0x00000000 #check that value at 0x00 is really 0x00000000 -id_offset = @0x00:BE - -codec = PCM16LE -channels = 2 -sample_rate = 32000 -start_offset = 0x04 -num_samples = data_size -loop_start_sample = 0 -loop_end_sample = data_size -``` - -**Kim Possible: What's the Switch (PS2) .str.txth** -``` -codec = PSX -interleave = 0x2000 -channels = 2 -sample_rate = 48000 -num_samples = data_size -interleave_last = auto -``` - -**Manhunt (Xbox) .rib.txth** -``` -codec = XBOX -codec_mode = 1 #interleaved XBOX -interleave = 0xD800 - -channels = 12 -sample_rate = 44100 -start_offset = 0x00 -num_samples = data_size -``` - -**Pitfall The Lost Expedition (PC) .txth** -``` -codec = DVI_IMA -interleave = 0x80 -start_offset = 0x00 -channels = 2 -sample_rate = 44100 -num_samples = data_size -``` - -**Spy Hunter (GC) .pcm.txth** -``` -codec = PCM8 -sample_rate = 32000 -channels = 1 -start_offset = 0 -num_samples = data_size -``` - -**Ultimate Board Game Collection (Wii) .dsp.txth** -``` -codec = NGC_DSP -interleave = 0x10000 - -channels = 2 -start_offset = 0x00 - -num_samples = @0x00:BE -sample_rate = @0x08:BE -loop_flag = @0x0C:BE$2 -sample_type = bytes -loop_start_sample = @0x10:BE -loop_end_sample = @0x14:BE - -coef_offset = 0x1c -coef_spacing = 0x10000 -coef_endianness = BE -``` -**Aladdin in Nasira's Revenge (PS1) .cvs.txth** -``` -codec = PSX -interleave = 0x10 -sample_rate = 24000 -channels = 1 -padding_size = auto-empty -num_samples = data_size -``` - -**Shikigami no Shiro - Nanayozuki Gensoukyoku (PS2) bgm.txth** -``` -codec = PSX -interleave = 0x1000 - -# this .txth is meant to be loaded directly -header_file = data/SLPM_660.69 -body_file = data/BGM.BIN - -channels = 2 - -# subsong headers at 0x1A5A40, entry size 0x14, total 58 * 0x14 = 0x488 -subsong_count = 58 -subsong_offset = 0x14 -base_offset = 0x1A5A40 - -sample_rate = @0x00 -start_offset = @0x04 * 0x800 #in sectors - -sample_type = bytes -num_samples = @0x08 * channels #in 1ch sizes -loop_start_sample = @0x0c * channels -loop_end_sample = @0x10 * channels - -data_size = @0x08 * channels #for bitrate -``` - -**Dragon Poker (Mobile) .snd.txth** -``` -# parse MP3 inside the .snd -subfile_extension = mp3 -subfile_offset = 0x14 -#subfile_size = @0x10 - -# manually set looping -codec = MPEG -start_offset = 0x14 -num_samples = data_size -loop_start_sample = 0 -loop_end_sample = data_size -``` - -**Simple 2000 Series Vol. 120 - The Saigo no Nihonhei (PS2) .xag.txth** -``` -header_file = TSNDDRVC.IRX - -name_table = .names.txt -base_offset = 0xAC3c + name_value - -codec = PSX -interleave = @0x10 -sample_rate = @0x0A$2 * 48000 / 4096 #pitch value -channels = @0x0D$1 -loop_start_sample = @0x0E$1 * interleave / 2 / 0x10 * 28 -loop_flag = @0x0F$1 - -padding_size = auto-empty -loop_end_sample = data_size -num_samples = data_size -``` -*.names.txt* -``` -# offset-to-header within TSNDDRVC.IRX at around 0xAC3C + position * 0x18 -BGM001.XAG: 0x00 -BGM002.XAG: 0x18 -BGM000.XAG: 0x30 -BGM003.XAG: 0x48 -BGM008.XAG: 0xA8 -BGM010.XAG: 0xD8 -BGM011.XAG: 0xF0 -BGM012.XAG: 0x108 -PAD.XAG : 0x150 -JIN002.XAG: 0x168 -JIN003.XAG: 0x180 -``` - - -** Grandia (PS1) ** -``` -header_file = GM1.IDX -body_file = GM1.STZ - -subsong_count = 394 #last doesn't have size though -subsong_offset = 0x04 - -subfile_offset = (@0x00 & 0xFFFFF) * 0x800 -subfile_extension = seb -subfile_size = ((@0x04 - @0x00) & 0xFFFFF) * 0x800 -``` +# TXTH FORMAT + +TXTH is a simple text file that uses text commands to simulate a header for files unsupported by vgmstream, mainly headerless audio. + +When an unsupported file is loaded (for instance "bgm01.snd"), vgmstream tries to find a TXTH header in the same dir, in this order: +- `(filename.ext).txth` +- `.(ext).txth` +- `.txth` + +If found and parsed correctly (the .txth may be rejected if incorrect commands are found) vgmstream will try to play the file as described. Extension must be accepted/added to vgmstream (plugins like foobar2000 only load extensions from a whitelist in formats.c), or one could rename to any supported extension (like .vgmstream), or leave the file extensionless. + +You can also use `.(sub).(ext).txth` (if the file is `filename.sub.ext`), to allow mixing slightly different files in the same folder. The `sub` part doesn't need to be an extension, for example: +- `001.1ch.str`, `001.1ch.str` may use `.1ch.txth` +- `003.2ch.str`, `003.2ch.str` may use `.2ch.txth` +- etc + +## Example of a TXTH file +For an unsupported `bgm01.vag` this would be a simple TXTH for it: +``` +codec = PSX #data uses PS-ADPCM +sample_rate = @0x10$2 #get sample rate at offset 0x10, 16 bit value +channels = @0x14 #get number of channels at offset 14 +interleave = 0x1000 #fixed value +start_offset = 0x100 #data starts after exactly this value +num_samples = data_size #find automatically number of samples in the file +loop_flag = auto #find loop points in PS-ADPCM +``` +A text file with the above commands must be saved as `.vag.txth` or `.txth` (preferably the former), notice it starts with a "." (dot). On Windows files starting with a dot can be created by appending a dot at the end when renaming: `.txth.` + +While the main point is playing the file, many of TXTH's features are aimed towards keeping original data intact, for documentation and preservation purposes; try leaving data as untouched as possible and consider how the game plays the file, as there is a good chance some feature can mimic it. + + +## Available commands + +The file is made of lines with `key = value` commands describing a header. Commands are all case sensitive and spaces are optional: `key=value`, `key = value`, and so on are all ok. Comments start with # and can be inlined. + +The parser is fairly simple and may be buggy or unexpected in some cases. The order of keys is variable but some things won't work if others aren't defined (ex. bytes-to-samples may not work without channels or interleave) or need to be done in a certain order (due to technical reasons) as explained below. + +To get a file playing you need to correctly set, at least: `codec` and sometimes `interleave`, `sample_rate`, `channels` and `num_samples`, or use the "subfile" feature. + +### VALUES +The following can be used in place of `(value)` for `(key) = (value)` commands. +- `(number)`: constant number in dec/hex, unsigned (no +10 or -10). + * Examples: `44100, 40, 0x40 (decimal=64)` +- `(offset)`: read a value at offset inside the file, format being `@(number)[:LE|BE][$1|2|3|4]` + * `@(number)`: offset of the value (required) + * if `base_offset` is defined this value is modified (see later) + * `:LE|BE`: value is little/big endian (optional, defaults to LE) + * `$1|2|3|4`: value has size of 8/16/24/32 bit (optional, defaults to 4) + * Example: `@0x10:BE$2` means `get big endian 16b value at 0x10` +- `(field)`: uses current value of some fields. Accepted strings: + - `interleave, interleave_last, channels, sample_rate, start_offset, data_size, num_samples, loop_start_sample, loop_end_sample, subsong_count, subsong_offset, subfile_offset, subfile_size, name_valueX` +- `(other)`: other special values for certain keys, described per key + + +The above may be combined with math operations (+-*/&): `(key) = (number) (op) (offset) (op) (field) (...)` + +### KEYS + +#### CODEC [REQUIRED] +Sets codec used to encode the data. Some codecs need interleave or other config +as explained below, but often will use default values. Accepted codec strings: +``` +# - PSX PlayStation ADPCM +# * For many PS1/PS2/PS3 games +# * Interleave is multiple of 0x10 (default), often +0x1000 +# - PSX_bf PlayStation ADPCM with bad flags +# * Variation with garbage data, for rare PS2 games +# - XBOX Xbox IMA ADPCM (mono/stereo) +# * For many XBOX games, and some PC games +# * Special interleave is multiple of 0x24 (mono) or 0x48 (stereo) +# - DSP|NGC_DSP Nintendo GameCube ADPCM +# * For many GC/Wii/3DS games +# * Interleave is multiple of 0x08 (default), often +0x1000 +# * Must set decoding coefficients (coef_offset/spacing/etc) +# * Should set ADPCM state (hist_offset/spacing/etc) +# - DTK|NGC_DTK Nintendo ADP/DTK ADPCM +# * For rare GC games +# - PCM16LE PCM 16-bit little endian +# * For many games (usually on PC) +# * Interleave is multiple of 0x2 (default) +# - PCM16BE PCM 16-bit big endian +# * Variation for certain consoles (GC/Wii/PS3/X360/etc) +# - PCM8 PCM 8-bit signed +# * For some games (usually on PC) +# * Interleave is multiple of 0x1 (default) +# - PCM8_U PCM 8-bit unsigned +# * Variation with modified encoding +# - PCM8_U_int PCM 8-bit unsigned (interleave block) +# * Variation with modified encoding +# - IMA IMA ADPCM (mono/stereo) +# * For some PC games, and rarely consoles +# * Special interleave is multiple of 0x1, often +0x80 +# - DVI_IMA IMA ADPCM (DVI order) +# * Variation with modified encoding +# - AICA Yamaha AICA ADPCM (mono/stereo) +# * For some Dreamcast games, and some arcade (Naomi) games +# * Special interleave is multiple of 0x1 +# - APPLE_IMA4 Apple Quicktime IMA ADPCM +# * For some Mac/iOS games +# - MS_IMA Microsoft IMA ADPCM +# * For some PC games +# * Interleave (frame size) varies, often multiple of 0x100 [required] +# - MSADPCM Microsoft ADPCM (mono/stereo) +# * For some PC games +# * Interleave (frame size) varies, often multiple of 0x100 [required] +# - SDX2 Squareroot-delta-exact 8-bit DPCM +# * For many 3DO games +# - MPEG MPEG Audio Layer file (MP1/2/3) +# * For some games (usually PC/PS3) +# * May set skip_samples +# - ATRAC3 Sony ATRAC3 +# * For some PS2 and PS3 games +# * Interleave (frame size) can be 0x60/0x98/0xC0 * channels [required] +# * Should set skip_samples (more than 1024+69 but varies) +# - ATRAC3PLUS Sony ATRAC3plus +# * For many PSP games and rare PS3 games +# * Interleave (frame size) can be: [required] +# Mono: 0x0118|0178|0230|02E8 +# Stereo: 0x0118|0178|0230|02E8|03A8|0460|05D0|0748|0800 +# 6/8 channels: multiple of one of the above +# * Should set skip_samples (more than 2048+184 but varies) +# - XMA1 Microsoft XMA1 +# * For early X360 games +# - XMA2 Microsoft XMA2 +# * For later X360 games +# - FFMPEG Any headered FFmpeg format +# * For uncommon games +# * May set skip_samples +# - AC3 AC3/SPDIF +# * For few PS2 games +# * Should set skip_samples (around 256 but varies) +# - PCFX PC-FX ADPCM +# * For many PC-FX games +# * Interleave is multiple of 0x1, often +0x8000 +# * Sample rate may be ~31468/~15734/~10489/~7867 +# - PCM4 PCM 4-bit signed +# * For early consoles +# - PCM4_U PCM 4-bit unsigned +# * Variation with modified encoding +# - OKI16 OKI ADPCM with 16-bit output (not VOX/Dialogic 12-bit) +# * For few PS2 games (Sweet Legacy, Hooligan) +# - AAC Advanced Audio Coding (raw without .mp4) +# * For some 3DS games and many iOS games +# * Should set skip_samples (around 1024 but varies) +codec = (codec string) +``` + +#### CODEC VARIATIONS +Changes the behavior of some codecs: +``` +# - NGC_DSP: 0=normal interleave, 1=byte interleave, 2=no interleave +# - XMA1|XMA2: 0=dual multichannel (2ch xN), 1=single multichannel (1ch xN) +# - XBOX: 0=standard (mono or stereo interleave), 1=force mono interleave mode +# - PCFX: 0=standard, 1='buggy encoder' mode, 2/3=same as 0/1 but with double volume +# - PCM4|PCM4_U: 0=low nibble first, 1=high nibble first +# - others: ignored +codec_mode = (variation) +``` + +#### (deprecated) VALUE MODIFIERS +*Use inline math instead of this.* + +Changes next read to: `(key) = (value) */+- value_(op)`. Set to 0 when done using, as it affects ANY value. Priority is as listed. +``` +value_mul|value_* = (value) +value_div|value_/ = (value) +value_add|value_+ = (value) +value_sub|value_- = (value) +``` + +#### INTERLEAVE / FRAME SIZE [REQUIRED depending on codec] +This value changes how data is read depending on the codec: +- For mono/interleaved codecs it's the amount of data between channels, and while optional (defaults described in the "codec" section) you'll often need to set it to get proper sound. +- For codecs with custom frame sizes (MSADPCM, MS-IMA, ATRAC3/plus) means frame size and is required. +- Interleave 0 means "stereo mode" for codecs marked as "mono/stereo", and setting it will usually force mono-interleaved mode. + +Special values: +- `half_size`: sets interleave as data_size / channels automatically +``` +interleave = (value)|half_size +``` + +#### INTERLEAVE IN THE LAST BLOCK +In some files with interleaved data the last block (`interleave * channels`) of data is smaller than normal, so `interleave` is smaller for that block. Setting this fixes decoding glitches at the end. + +Note that this doesn't affect files with padding data in the last block (as the `interleave` itself is constant). + +Special values: +- `auto`: calculate based on channels, interleave and data_size/start_offset +``` +interleave_last = (value)|auto +``` + +#### ID VALUES +Validates that `id_value` (normally set as constant value) matches value read at `id_offset`. The file will be rejected and won't play if values don't match. + +Can be redefined several times, it's checked whenever a new id_offset is found. +``` +id_value = (value) +id_offset = (value) +``` + +#### NUMBER OF CHANNELS [REQUIRED] +``` +channels = (value) +``` + +#### MUSIC FREQUENCY [REQUIRED] +``` +sample_rate = (value) +``` + +#### DATA START +Where encoded data actually starts, after the header part. Defaults to 0. +``` +start_offset = (value) +``` + +#### DATA SIZE +Special variable that can be used in sample values. Defaults to `(file_size - start_offset)`, re-calculated when `start_offset` is set. With multiple subsongs, `block_size` or padding are set this it's recalculated as well. + +If data_size is manually set it stays constant and won't be auto changed. +``` +data_size = (value) +``` + +#### DATA PADDING +Some files have extra padding at the end that is meant to be ignored. This adjusts the padding in `data_size`, manually or auto-calculated. + +Special values (for PS-ADPCM only): +- `auto`: discards null frames +- `auto-empty`: discards null and 'empty' frames (for games with weird padding) +``` +padding_size = (value)|auto|auto-empty +``` + +#### SAMPLE MEANINGS +Modifies the meaning of sample fields when set *before* them. + +Accepted values: +- `samples`: exact sample (default) +- `bytes`: automatically converts bytes/offset to samples (applies after */+-& modifiers) +- `blocks`: same as bytes, but value is given in blocks/frames + * Value is internally converted from blocks to bytes first: `bytes = (value * interleave*channels)` + +Some codecs can't convert bytes-to-samples at the moment: `FFMPEG`. For XMA1/2, bytes does special parsing, with loop values being bit offsets within data (as XMA has a peculiar way to loop). +``` +sample_type = samples|bytes|blocks +``` + +#### SAMPLE VALUES [REQUIRED (num_samples)] +Special values: +- `data_size`: automatically converts bytes-to-samples +``` +num_samples = (value)|data_size +loop_start_sample = (value) +loop_end_sample = (value)|data_size +``` + +#### LOOP SETTING +Force loop on or off, as loop start/end may be defined but not used. If not set, by default it loops when loop_end_sample is defined and less than num_samples. + +Special values: +- auto: tries to autodetect loop points for PS-ADPCM data using data loop flags. + +Sometimes games give loop flags different meaning, so behavior can be tweaked by defining `loop_behavior` before `loop_flag`: +- `default`: values 0 or 0xFFFF/0xFFFFFFFF (-1) disable looping, but not 0xFF (loop endlessly) +- `negative`: values 0xFF/0xFFFF/0xFFFFFFFF (-1) enable looping +- `positive`: values 0xFF/0xFFFF/0xFFFFFFFF (-1) disable looping + +``` +loop_negative = default|negative|positive +loop_flag = (value)|auto +``` + +#### LOOP START/END MODIFIER +For XMA1/2 + sample_type=bytes it means loop subregion, if read after loop values. + +For other codecs its added to loop start/end, if read before loop values (a format may rarely have rough loop offset/bytes, then a loop adjust in samples). + +``` +loop_adjust = (value) +``` + +#### ENCODER DELAY +Beginning samples to skip, a.k.a. priming samples or encoder delay, that some codecs use to "warm up" the decoder. This is needed for proper gapless support. + +Supported codecs: `ATRAC3/ATRAC3PLUS/XMA/FFMPEG/AC3/AAC` +``` +skip_samples = (value) +``` + +#### DSP DECODING COEFFICIENTS [REQUIRED for DSP] +DSP needs a "coefs" list to decode correctly. These are 8*2 16-bit values per channel, starting from `coef_offset`. + +Usually each channel uses its own list, so we may need to set separation per channel, usually 0x20 (16 values * 2 bytes). So channel N coefs are read at `coef_offset + coef_spacing * N` + +Those 16-bit coefs can be little or big endian (usually BE), set `coef_endianness` directly or in an offset value where ´0=LE, >0=BE´. + +While the coef table is almost always included per-file, some games have their coef table in the executable or precalculated somehow. You can set inline coefs instead of coef_offset. Format is a long string of bytes (optionally space-separated) like `coef_table = 0x1E02DE01 3C0C0EFA ...`. You still need to set `coef_spacing` and `coef_endianness` though. +``` +coef_offset = (value) +coef_spacing = (value) +coef_endianness = BE|LE|(value) +coef_table = (string) +``` + +#### ADPCM STATE +Some ADPCM codecs need to set up their initial or "history" state, normally one or two 16-bit PCM samples per channel, starting from `hist_offset`. + +Usually each channel uses its own state, so we may need to set separation per channel. + +State values can be little or big endian (usually BE for DSP), set `hist_endianness` directly or in an offset value where ´0=LE, >0=BE´. + +Normally audio starts with silence or hist samples are set to zero and can be ignored, but it does affect a bit resulting output. + +Currently used by DSP. +``` +hist_offset = (value) +hist_spacing = (value) +hist_endianness = BE|LE|(value) +``` + +#### HEADER/BODY SETTINGS +Changes internal header/body representation to external files. + +TXTH commands are done on a "header", and decoding on "body". When loading an unsupported file it becomes the "base" file +that loads the .txth, and is both header and body. + +You can alter those, mainly for files that split header and body in separate files (load base file and txth sets header on another file). It's also possible to load the .txth directly with a set body, as a sort of "reverse TXTH" (useful with bigfiles, as you could have one .txth per song). + +Allowed values: +- (filename): open any file, subdirs also work (dir/filename) +- *.(extension): opens with same name as the "base" file (the one you open, not the .txth) plus another extension +- null: unloads file and goes back to defaults (body/header = base file). +``` +header_file = (filename)|*.(extension)|null +body_file = (filename)|*.(extension)|null +``` + +#### SUBSONGS +Sets the number of subsongs in the file, adjusting reads per subsong N: `value = @(offset) + subsong_offset*N`. number/constants values aren't adjusted though. + +Mainly for bigfiles with consecutive headers per subsong, set subsong_offset to 0 when done as it affects any reads. The current subsong number is handled externally by plugins or TXTP. +``` +subsong_count = (value) +subsong_offset = (value) +``` + +#### NAMES +Sets the name of the stream, most useful when used with subsongs. TXTH will read a string at `name_offset`, with `name_size characters`. + +`name_size` defaults to 0, which reads until null-terminator or a non-ascii character is found. + +`name_offset` can be a (number) value, but being an offset it's also adjusted by subsong_offset. +``` +name_offset = (value) +name_size = (value) +``` + +#### SUBFILES +Tells TXTH to parse a full file (ex. an Ogg) at `subfile_offset`, with size of `subfile_size` (defaults to `file size - subfile_offset` if not set). This is useful for files that are just container of other files, so you don't have to remove the extra data (since it could contain useful stuff like loop info). + +Internal subfile extension can be changed to `subfile_extension` if needed, as vgmstream won't accept unknown extensions (for example if your file uses .vgmstream or .pogg you may need to set subfile_extension = ogg). + +Setting any of those three will trigger this mode (it's ok to set offset 0). Once triggered most fields are ignored, but not all, explained later. This will also set some values like `channels` or `sample_rate` if not set for calculations/convenience. +``` +subfile_offset = (value) +subfile_size = (value) +subfile_extension = (string) +``` + +#### CHUNK DEINTERLEAVING +Some files interleave data chunks, for example 3 stereo songs pasted together, alternating 0x10000 bytes of data each. These settings allow vgmstream to play one of the chunks while ignoring the rest (read 0x10000 data, skip 0x10000*2). +File is first "dechunked" then played with using other settings (`start_offset` would point within the internal dechunked" file). It can be used to remove garbage data that affects decoding, too. + + +You need to set: +- `chunk_count`: total number of interleaved chunks (ex. 3=3 interleaved songs) +- `chunk_number`: first chunk to start (ex. 1=0x00000, 2=0x10000, 3=0x20000...) + * If you set `subsong_count` first `chunk_number` will be auto-set per subsong (subsong 1 starts from chunk number 1, subsong 2 from chunk 2, etc) +- `chunk_start`: absolute offset where chunks start (normally 0x00) +- `chunk_size`: amount of data in a single chunk (ex. 0x10000) +For fine-tuning you can optionally set (before `chunk_size`, for reasons): +- `chunk_header_size`: header to skip before chunk data (part of chunk_size) +- `chunk_data_size`: actual data size (part of chunk_size, rest is header/padding) + +So, if you set size to 0x1000, header_size 0x100, data_size is implicitly 0xF00, or if size is 0x1000 and data_size 0x800 last 0x200 is ignored padding. Use combinations of the above to make vgmstream "see" only actual codec data. +``` +chunk_count = (value) +chunk_number = (value) +chunk_start = (value) +chunk_header_size = (value) +chunk_data_size = (value) +chunk_size = (value) +``` + +#### NAME TABLE +Some games have headers for all files pasted together separate from the actual data, but this order may be hard-coded or even alphabetically ordered by filename. In those cases you can set a "name table" that assigns constant values (one or many) to filenames. This table is loaded from an external text file (for clarity) and can be set to any name, for example `name_table = .names.txt` +``` +name_table = (filename) +``` + +Inside the table you define lines mapping a filename to a bunch of values, in this format: +``` +# base definition +(filename1): (value) +... +# may put multiple comma-separated values, spaces are ok +(filenameN) : (value1), (...) , (valueN) + +# put no name before the : to set default values + : (value1), (...), (valueN) +``` +Then I'll find your current file name, and you can then reference its numbers from the list as a `name_value` field, like `base_offset = name_value`, `start_offset = 0x1000 + name_value1`, `interleave = name_value5`, etc. `(filename)` can be with or without extension (like `bgm01.vag` or just `bgm01`), and if the file's name isn't found it'll use default values, and if those aren't defined you'll get 0 instead. Being "values" they can be use math or offsets too. + +You can use wildcards to match multiple names too (it stops on first name that matches), and UTF-8 names should work, case insensitive even. +``` +bgm_??_4: 4 # 4ch: files like bgm_00_4, bgm_01_4, etc +bgm*_M: 1 # 1ch: some files end with _M for mono +bgm*: 2 # 2ch: all other files, notice order matters +``` + +While you can put anything in the numbers, this feature is meant to be used to store some number that points to the actual data inside a real multi-header, that could be set with `header_file`. If you need to store many constant values there is good chance this can be supported in some better way. + + +#### BASE OFFSET MODIFIER +You can set a default offset that affects next `@(offset)` reads making them `@(offset + base_offset)`, for cleaner parsing (particularly interesting when combined with the `name_list). + +For example instead of `channels = @0x714` you could set `base_offset = 0x710, channels = @0x04`. Set to 0 when you want to disable it. +``` +base_offset = (value) +``` + + +## Complex usages + +### Temporary values +Most commands are evaluated and calculated immediatedly, every time they are found. This is by design, as it can be used to adjust and trick for certain calculations. + +It makes TXTHs a bit harder to follow, as they are order dependant, but otherwise it's hard to accomplish some things or others become ambiguous. + + +For example, normally you are given a data_size in bytes, that can be used to calculate num_samples for all channels. +``` +channels = 2 +sample_type = bytes +num_samples = @0x10 #calculated from data_size +``` + +But sometimes this size is for a single channel only (even though the file may be stereo). You can set temporally change the channel number to force a correct calculation. +``` +channels = 1 #not the actual number of channels +sample_type = bytes +num_samples = @0x10 #calculated from channel_size +channels = 2 #change once calculations are done +``` +You can also use: +``` +channels = 2 +sample_type = bytes +num_samples = @0x10 * channels # resulting bytes is transformed to samples +``` + +Do note when using special values/strings like `data_size` in `num_samples` and `loop_end_samples` they must be alone to trigger. +``` +data_size = @0x100 +num_samples = data_size * 2 # doesn't tranform bytes-to-samples (do it before? after?) +``` +``` +data_size = @0x100 * 2 +num_samples = data_size # ok +``` +Also beware of order: +``` +start_offset = 0x200 # recalculated data_size +num_samples = data_size # transforms bytes-to-samples +data_size = @0x100 # useless as num_samples is already transformed +``` + + +### Redefining values +Some commands alter the function of all next commands and can be redefined as needed: +``` +samples_type = bytes +num_samples = @0x10 + +samples_type = sample +loop_end_sample = @0x14 +``` + +### External files +When setting external files all commands are done on the "header" file, but with some creativity you can read in multiple files. +``` +body_file = bgm01.bdy +header_file = bgm01.hdr +channels = @0x10 #base info in bgm01.hdr +header_file = bgm01.bdy +coef_offset = 0x00 #DSP coefs in bgm01.bdy +``` +Note that DSP coefs are special in that aren't read immediately, and will use *last* header_file set. + + +### Resetting values +Values may need to be reset (to 0 or other sensible value) when done. Subsong example: +``` +subsong_count = 5 +subsong_offset = 0x20 # there are 5 subsong headers, 0x20 each +channel_count = @0x10 # reads channels at 0x10+0x20*subsong +# 1st subsong: 0x10+0x20*0: 0x10 +# 2nd subsong: 0x10+0x20*1: 0x30 +# 2nd subsong: 0x10+0x20*2: 0x50 +# ... +start_offset = @0x14 # reads offset within data at 0x14+0x20*subsong + +subsong_offset = 0 # reset value +sample_rate = 0x04 # sample rate is the same for all subsongs +# Nth subsong ch: 0x04+0x00*N: 0x08 +``` + +### Math +Sometimes header values are in "sectors" or similar concepts (typical in DVD games), and need to be adjusted to a real value using some complex math: +``` +sample_type = bytes +start_offset = @0x10 * 0x800 # 0x15 * DVD sector size, for example +``` + +You can use `+-*/&` operators, and also certain fields' values: +``` +num_samples = @0x10 * channels # byte-to-samples of channel_size +``` +`data_size` is a special value for `num_samples` and `loop_end_sample` and will always convert as bytes-to-samples, though. + + +Priority is left-to-right. Do add brackets though, they are accounted for and if they are implemented in the future your .txth *will* break with impunity. +``` +# normal priority +data_size = @0x10 * 0x800 + 0x800 +# also works +data_size = (@0x10 + 1) * 0x800 +# same as above but don't do this +# (may become @0x10 + (1 * 0x800) in the future +data_size = @0x10 + 1 * 0x800 +# doesn't work at the moment, so reorder as (1 * 0x800) + @0x10 +data_size = @0x10 + (1 * 0x800) +# fails, wrong bracket count +data_size = (@0x10 + 1 * 0x800 +# fails, wrong bracket count +data_size = )@0x10 + 1 * 0x800 +``` + +If a TXTH needs too many calculations it may be better to implement directly in vgmstream though, consider reporting. + + +### Modifiers +Remnant of simpler math (priority is fixed to */+-), *shouldn't be needed anymore*. + +``` +value_multiply = 0x800 +start_offset = @0x10 +value_multiply = 0 +``` + +``` +value_add = 1 +channels = @0x08 +value_add = 0 + +value_multiply = channels +sample_type = bytes +num_samples = @0x10 +value_multiply = 0 +``` + +``` +value_add = 0x10 +value_mul = 0x800 +start_offset = @0x10 +``` + +### Subfiles +Sometimes a file is just a wrapper for another common format. In those cases you can tell TXTH to just play the internal format: +``` +subfile_offset = 0x20 # tell TXTH to parse a full file (ex. .ogg) at this offset +subfile_size = @0x10 # defaults to (file size - subfile_offset) if not set +subfile_extension = ogg # may be ommited if subfile extension is the same + +# many fields are ignored +codec = PCM16LE +interleave = 0x1000 +channels = 2 + +# a few fields are applied +sample_rate = @0x08 +num_samples = @0x10 +loop_start_sample = @0x14 +loop_end_sample = @0x18 +``` +Most fields can't be changed after parsing since doesn't make much sense technically, as the parsed subfile should supply them. You can set them to use bytes-to-samples conversions, though. +``` +# parses subfile at start with some num_samples +subfile_offset = 0x20 +# force recalculation of num_samples +codec = PSX +start_offset = 0x40 +num_samples = data_size +``` + +### Chunks +Chunks affect some values (padding size, data size, etc) and are a bit sensitive to order at the moment, due to technical complexities: +``` +# Street Fighter EX3 (PS2) + +# base config is defined normally +codec = PSX +sample_rate = 44100 +channels = 2 +interleave = 0x8000 + +# set subsong number instead of chunk_number for subsongs +subsong_count = 26 +#chunk_number = 1 +chunk_start = 0 +chunk_size = 0x10000 +chunk_count = 26 + +# after setting chunks (sizes vary when 'dechunking') +start_offset = 0x00 +padding_size = auto-empty +num_samples = data_size +``` + +Subfiles and chunks can coexist: +``` +# Gitaroo Man (PSP) + +# 3 interleaved RIFF files +subsong_count = 3 +chunk_start = 0 +chunk_size = 0x2800 +chunk_count = 3 + +# the 3 de-interleaved chunks are treated and parsed as a subsong +subfile_offset = 0 +subfile_size = @0x04 + 0x08 #RIFF size +subfile_extension = at3 +``` + +It can be used to make blocks with padding playable: +``` +# Mortal Kombat: Deception (PS2) +codec = PSX +interleave = 0x3F40 +sample_rate = 32000 +channels = 2 + +chunk_number = 1 +chunk_count = 1 +chunk_start = 0x00 +chunk_data_size = interleave * channels +chunk_size = 0x8000 + +num_samples = data_size +``` + +## Examples + +**Colin McRae DiRT (PC) .wip.txth** +``` +id_value = 0x00000000 #check that value at 0x00 is really 0x00000000 +id_offset = @0x00:BE + +codec = PCM16LE +channels = 2 +sample_rate = 32000 +start_offset = 0x04 +num_samples = data_size +loop_start_sample = 0 +loop_end_sample = data_size +``` + +**Kim Possible: What's the Switch (PS2) .str.txth** +``` +codec = PSX +interleave = 0x2000 +channels = 2 +sample_rate = 48000 +num_samples = data_size +interleave_last = auto +``` + +**Manhunt (Xbox) .rib.txth** +``` +codec = XBOX +codec_mode = 1 #interleaved XBOX +interleave = 0xD800 + +channels = 12 +sample_rate = 44100 +start_offset = 0x00 +num_samples = data_size +``` + +**Pitfall The Lost Expedition (PC) .txth** +``` +codec = DVI_IMA +interleave = 0x80 +start_offset = 0x00 +channels = 2 +sample_rate = 44100 +num_samples = data_size +``` + +**Spy Hunter (GC) .pcm.txth** +``` +codec = PCM8 +sample_rate = 32000 +channels = 1 +start_offset = 0 +num_samples = data_size +``` + +**Ultimate Board Game Collection (Wii) .dsp.txth** +``` +codec = NGC_DSP +interleave = 0x10000 + +channels = 2 +start_offset = 0x00 + +num_samples = @0x00:BE +sample_rate = @0x08:BE +loop_flag = @0x0C:BE$2 +sample_type = bytes +loop_start_sample = @0x10:BE +loop_end_sample = @0x14:BE + +coef_offset = 0x1c +coef_spacing = 0x10000 +coef_endianness = BE +``` +**Aladdin in Nasira's Revenge (PS1) .cvs.txth** +``` +codec = PSX +interleave = 0x10 +sample_rate = 24000 +channels = 1 +padding_size = auto-empty +num_samples = data_size +``` + +**Shikigami no Shiro - Nanayozuki Gensoukyoku (PS2) bgm.txth** +``` +codec = PSX +interleave = 0x1000 + +# this .txth is meant to be loaded directly +header_file = data/SLPM_660.69 +body_file = data/BGM.BIN + +channels = 2 + +# subsong headers at 0x1A5A40, entry size 0x14, total 58 * 0x14 = 0x488 +subsong_count = 58 +subsong_offset = 0x14 +base_offset = 0x1A5A40 + +sample_rate = @0x00 +start_offset = @0x04 * 0x800 #in sectors + +sample_type = bytes +num_samples = @0x08 * channels #in 1ch sizes +loop_start_sample = @0x0c * channels +loop_end_sample = @0x10 * channels + +data_size = @0x08 * channels #for bitrate +``` + +**Dragon Poker (Mobile) .snd.txth** +``` +# parse MP3 inside the .snd +subfile_extension = mp3 +subfile_offset = 0x14 +#subfile_size = @0x10 + +# manually set looping +codec = MPEG +start_offset = 0x14 +num_samples = data_size +loop_start_sample = 0 +loop_end_sample = data_size +``` + +**Simple 2000 Series Vol. 120 - The Saigo no Nihonhei (PS2) .xag.txth** +``` +header_file = TSNDDRVC.IRX + +name_table = .names.txt +base_offset = 0xAC3c + name_value + +codec = PSX +interleave = @0x10 +sample_rate = @0x0A$2 * 48000 / 4096 #pitch value +channels = @0x0D$1 +loop_start_sample = @0x0E$1 * interleave / 2 / 0x10 * 28 +loop_flag = @0x0F$1 + +padding_size = auto-empty +loop_end_sample = data_size +num_samples = data_size +``` +*.names.txt* +``` +# offset-to-header within TSNDDRVC.IRX at around 0xAC3C + position * 0x18 +BGM001.XAG: 0x00 +BGM002.XAG: 0x18 +BGM000.XAG: 0x30 +BGM003.XAG: 0x48 +BGM008.XAG: 0xA8 +BGM010.XAG: 0xD8 +BGM011.XAG: 0xF0 +BGM012.XAG: 0x108 +PAD.XAG : 0x150 +JIN002.XAG: 0x168 +JIN003.XAG: 0x180 +``` + + +** Grandia (PS1) ** +``` +header_file = GM1.IDX +body_file = GM1.STZ + +subsong_count = 394 #last doesn't have size though +subsong_offset = 0x04 + +subfile_offset = (@0x00 & 0xFFFFF) * 0x800 +subfile_extension = seb +subfile_size = ((@0x04 - @0x00) & 0xFFFFF) * 0x800 +``` diff --git a/src/meta/ogg_opus.c b/src/meta/ogg_opus.c index dd1d3409..29717d43 100644 --- a/src/meta/ogg_opus.c +++ b/src/meta/ogg_opus.c @@ -1,174 +1,174 @@ -#include "meta.h" -#include "../coding/coding.h" - - -static int get_ogg_page_size(STREAMFILE *streamFile, off_t page_offset, off_t *out_data_offset, size_t *out_page_size); -static int ogg_get_num_samples(STREAMFILE *streamFile, off_t start_offset); - -/* Ogg Opus - standard Opus with optional looping comments [The Pillars of Earth (PC), Monster Boy and the Cursed Kingdom (Switch)] */ -VGMSTREAM * init_vgmstream_ogg_opus(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset, data_offset = 0; - size_t page_size = 0; - int loop_flag, channel_count, original_rate; - int loop_start = 0, loop_end = 0; - - - /* checks */ - /* .opus: standard, .lopus: fake extension for plugins - * .ogg: less common, .logg: same */ - if (!check_extensions(streamFile, "opus,lopus,ogg,logg")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x4F676753) /* "OggS" */ - goto fail; - /* see: https://tools.ietf.org/html/rfc7845.html */ - - start_offset = 0x00; - - /* parse 1st page: opus head */ - if (!get_ogg_page_size(streamFile, start_offset, &data_offset, &page_size)) - goto fail; - if (read_32bitBE(data_offset+0x00,streamFile) != 0x4F707573 && /* "Opus" */ - read_32bitBE(data_offset+0x04,streamFile) != 0x48656164) /* "Head" */ - goto fail; - /* 0x01: version 1, fixed */ - channel_count = read_8bit(data_offset+0x09,streamFile); - /* 0x0A: skip samples */ - original_rate = read_32bitLE(data_offset+0x0c,streamFile); - /* 0x10: gain */ - /* 0x12: mapping family */ - - /* parse 2nd page: opus tags (also mandatory) */ - if (!get_ogg_page_size(streamFile, start_offset+page_size, &data_offset, &page_size)) - goto fail; - if (read_32bitBE(data_offset+0x00,streamFile) != 0x4F707573 && /* "Opus" */ - read_32bitBE(data_offset+0x04,streamFile) != 0x54616773) /* "Tags" */ - goto fail; - - loop_flag = 0; - { - char user_comment[1024+1]; - off_t offset; - int vendor_size, comment_count, user_comment_size, user_comment_max; - int i; - int has_encoder_options = 0, has_title = 0; - - vendor_size = read_32bitLE(data_offset+0x08,streamFile); - comment_count = read_32bitLE(data_offset+0x0c+vendor_size,streamFile); - - /* parse comments */ - offset = data_offset + 0x0c + vendor_size + 0x04; - for (i = 0; i < comment_count; i++) { - user_comment_size = read_32bitLE(offset+0x00,streamFile); - user_comment_max = user_comment_size > 1024 ? 1024 : user_comment_size; - read_string(user_comment,user_comment_max+1, offset+0x04,streamFile); - - - /* parse loop strings */ - if (strstr(user_comment,"LOOP_START=")==user_comment) { /* Monster Boy and the Cursed Kingdom (Switch) */ - loop_start = atol(strrchr(user_comment,'=')+1); - loop_flag = (loop_start >= 0); - } - else if (strstr(user_comment,"LOOP_END=")==user_comment) { /* LOOP_START pair */ - loop_end = atol(strrchr(user_comment,'=')+1); - } - else if (strstr(user_comment,"ENCODER_OPTIONS=")==user_comment) { /* for detection */ - has_encoder_options = 1; - } - else if (strstr(user_comment,"TITLE=")==user_comment) { /* for detection */ - has_title = 1; - } - - - //;VGM_LOG("OggOpus: user_comment=%s\n", user_comment); - offset += 0x04 + user_comment_size; - } - - - /* Monster Boy has loop points for 44100hz (what), but Opus is resampled so - * they must be adjusted (with extra checks just in case). */ - if (loop_flag && original_rate < 48000 && has_encoder_options && has_title) { - float modifier = 48000.0f / (float)original_rate; - loop_start = loop_start * modifier; - loop_end = loop_end * modifier; - } - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_OGG_OPUS; - vgmstream->sample_rate = 48000; /* Opus always resamples to this */ - vgmstream->num_samples = ogg_get_num_samples(streamFile, 0x00); - vgmstream->loop_start_sample = loop_start; - vgmstream->loop_end_sample = loop_end; - -#ifdef VGM_USE_FFMPEG - { - vgmstream->codec_data = init_ffmpeg_offset(streamFile, start_offset, get_streamfile_size(streamFile)); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data); - /* FFmpeg+libopus handles skip samples ok, FFmpeg+opus doesn't */ - } -#else - goto fail; -#endif - - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - - -/* parse OggS's bizarre segment table */ -static int get_ogg_page_size(STREAMFILE *streamFile, off_t page_offset, off_t *out_data_offset, size_t *out_page_size) { - uint8_t segments; - size_t page_size = 0; - int i; - - if (read_32bitBE(page_offset+0x00,streamFile) != 0x4F676753) /* "OggS" */ - goto fail; - - /* read all segment sizes */ - segments = (uint8_t)read_8bit(page_offset+0x1a, streamFile); - for (i = 0; i < segments; i++) { - page_size += (uint8_t)read_8bit(page_offset + 0x1b + i, streamFile); - } - page_size += 0x1b + segments; - - if (out_data_offset) *out_data_offset = page_offset + 0x1b + segments; - if (out_page_size) *out_page_size = page_size; - return 1; -fail: - return 0; -} - -/* Ogg doesn't have num_samples info, must manually seek+read last granule - * (Xiph is insistent this is the One True Way). */ -static int ogg_get_num_samples(STREAMFILE *streamFile, off_t start_offset) { - uint32_t expected_id = 0x4F676753; - off_t offset = get_streamfile_size(streamFile) - 0x04-0x01-0x01-0x08-0x04-0x04-0x04; - - //todo better buffer reads (Ogg page max is 0xFFFF) - //lame way to force buffer, assuming it's around that - read_32bitBE(offset - 0x4000, streamFile); - - while (offset >= start_offset) { - uint32_t current_id = read_32bitBE(offset, streamFile); - if (current_id == expected_id) { /* if more checks are needed last page starts with 0x0004 */ - return read_32bitLE(offset+0x04+0x01+0x01, streamFile); /* get last granule = total samples (64b but whatevs) */ - } - - offset--; - } - - return 0; -} +#include "meta.h" +#include "../coding/coding.h" + + +static int get_ogg_page_size(STREAMFILE *streamFile, off_t page_offset, off_t *out_data_offset, size_t *out_page_size); +static int ogg_get_num_samples(STREAMFILE *streamFile, off_t start_offset); + +/* Ogg Opus - standard Opus with optional looping comments [The Pillars of Earth (PC), Monster Boy and the Cursed Kingdom (Switch)] */ +VGMSTREAM * init_vgmstream_ogg_opus(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset, data_offset = 0; + size_t page_size = 0; + int loop_flag, channel_count, original_rate; + int loop_start = 0, loop_end = 0; + + + /* checks */ + /* .opus: standard, .lopus: fake extension for plugins + * .ogg: less common, .logg: same */ + if (!check_extensions(streamFile, "opus,lopus,ogg,logg")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x4F676753) /* "OggS" */ + goto fail; + /* see: https://tools.ietf.org/html/rfc7845.html */ + + start_offset = 0x00; + + /* parse 1st page: opus head */ + if (!get_ogg_page_size(streamFile, start_offset, &data_offset, &page_size)) + goto fail; + if (read_32bitBE(data_offset+0x00,streamFile) != 0x4F707573 && /* "Opus" */ + read_32bitBE(data_offset+0x04,streamFile) != 0x48656164) /* "Head" */ + goto fail; + /* 0x01: version 1, fixed */ + channel_count = read_8bit(data_offset+0x09,streamFile); + /* 0x0A: skip samples */ + original_rate = read_32bitLE(data_offset+0x0c,streamFile); + /* 0x10: gain */ + /* 0x12: mapping family */ + + /* parse 2nd page: opus tags (also mandatory) */ + if (!get_ogg_page_size(streamFile, start_offset+page_size, &data_offset, &page_size)) + goto fail; + if (read_32bitBE(data_offset+0x00,streamFile) != 0x4F707573 && /* "Opus" */ + read_32bitBE(data_offset+0x04,streamFile) != 0x54616773) /* "Tags" */ + goto fail; + + loop_flag = 0; + { + char user_comment[1024+1]; + off_t offset; + int vendor_size, comment_count, user_comment_size, user_comment_max; + int i; + int has_encoder_options = 0, has_title = 0; + + vendor_size = read_32bitLE(data_offset+0x08,streamFile); + comment_count = read_32bitLE(data_offset+0x0c+vendor_size,streamFile); + + /* parse comments */ + offset = data_offset + 0x0c + vendor_size + 0x04; + for (i = 0; i < comment_count; i++) { + user_comment_size = read_32bitLE(offset+0x00,streamFile); + user_comment_max = user_comment_size > 1024 ? 1024 : user_comment_size; + read_string(user_comment,user_comment_max+1, offset+0x04,streamFile); + + + /* parse loop strings */ + if (strstr(user_comment,"LOOP_START=")==user_comment) { /* Monster Boy and the Cursed Kingdom (Switch) */ + loop_start = atol(strrchr(user_comment,'=')+1); + loop_flag = (loop_start >= 0); + } + else if (strstr(user_comment,"LOOP_END=")==user_comment) { /* LOOP_START pair */ + loop_end = atol(strrchr(user_comment,'=')+1); + } + else if (strstr(user_comment,"ENCODER_OPTIONS=")==user_comment) { /* for detection */ + has_encoder_options = 1; + } + else if (strstr(user_comment,"TITLE=")==user_comment) { /* for detection */ + has_title = 1; + } + + + //;VGM_LOG("OggOpus: user_comment=%s\n", user_comment); + offset += 0x04 + user_comment_size; + } + + + /* Monster Boy has loop points for 44100hz (what), but Opus is resampled so + * they must be adjusted (with extra checks just in case). */ + if (loop_flag && original_rate < 48000 && has_encoder_options && has_title) { + float modifier = 48000.0f / (float)original_rate; + loop_start = loop_start * modifier; + loop_end = loop_end * modifier; + } + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_OGG_OPUS; + vgmstream->sample_rate = 48000; /* Opus always resamples to this */ + vgmstream->num_samples = ogg_get_num_samples(streamFile, 0x00); + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + +#ifdef VGM_USE_FFMPEG + { + vgmstream->codec_data = init_ffmpeg_offset(streamFile, start_offset, get_streamfile_size(streamFile)); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data); + /* FFmpeg+libopus handles skip samples ok, FFmpeg+opus doesn't */ + } +#else + goto fail; +#endif + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + + +/* parse OggS's bizarre segment table */ +static int get_ogg_page_size(STREAMFILE *streamFile, off_t page_offset, off_t *out_data_offset, size_t *out_page_size) { + uint8_t segments; + size_t page_size = 0; + int i; + + if (read_32bitBE(page_offset+0x00,streamFile) != 0x4F676753) /* "OggS" */ + goto fail; + + /* read all segment sizes */ + segments = (uint8_t)read_8bit(page_offset+0x1a, streamFile); + for (i = 0; i < segments; i++) { + page_size += (uint8_t)read_8bit(page_offset + 0x1b + i, streamFile); + } + page_size += 0x1b + segments; + + if (out_data_offset) *out_data_offset = page_offset + 0x1b + segments; + if (out_page_size) *out_page_size = page_size; + return 1; +fail: + return 0; +} + +/* Ogg doesn't have num_samples info, must manually seek+read last granule + * (Xiph is insistent this is the One True Way). */ +static int ogg_get_num_samples(STREAMFILE *streamFile, off_t start_offset) { + uint32_t expected_id = 0x4F676753; + off_t offset = get_streamfile_size(streamFile) - 0x04-0x01-0x01-0x08-0x04-0x04-0x04; + + //todo better buffer reads (Ogg page max is 0xFFFF) + //lame way to force buffer, assuming it's around that + read_32bitBE(offset - 0x4000, streamFile); + + while (offset >= start_offset) { + uint32_t current_id = read_32bitBE(offset, streamFile); + if (current_id == expected_id) { /* if more checks are needed last page starts with 0x0004 */ + return read_32bitLE(offset+0x04+0x01+0x01, streamFile); /* get last granule = total samples (64b but whatevs) */ + } + + offset--; + } + + return 0; +} diff --git a/src/meta/opus.c b/src/meta/opus.c index 5007e758..4611e07c 100644 --- a/src/meta/opus.c +++ b/src/meta/opus.c @@ -1,439 +1,439 @@ -#include "meta.h" -#include "../coding/coding.h" -#include "../layout/layout.h" -#include "opus_interleave_streamfile.h" - -/* Nintendo OPUS - from Switch games, including header variations (not the same as Ogg Opus) */ - -static VGMSTREAM * init_vgmstream_opus(STREAMFILE *streamFile, meta_t meta_type, off_t offset, int32_t num_samples, int32_t loop_start, int32_t loop_end) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag = 0, channel_count; - off_t data_offset, multichannel_offset = 0; - size_t data_size, skip = 0; - - - if ((uint32_t)read_32bitLE(offset + 0x00,streamFile) != 0x80000001) - goto fail; - - channel_count = read_8bit(offset + 0x09, streamFile); - /* 0x0a: packet size if CBR, 0 if VBR */ - data_offset = offset + read_32bitLE(offset + 0x10, streamFile); - skip = read_16bitLE(offset + 0x1c, streamFile); - /* 0x1e: ? (seen in Lego Movie 2 (Switch)) */ - - /* recent >2ch info [Clannad (Switch)] */ - if ((uint32_t)read_32bitLE(offset + 0x20, streamFile) == 0x80000005) { - multichannel_offset = offset + 0x20; - } - - if ((uint32_t)read_32bitLE(data_offset, streamFile) != 0x80000004) - goto fail; - - data_size = read_32bitLE(data_offset + 0x04, streamFile); - - start_offset = data_offset + 0x08; - loop_flag = (loop_end > 0); /* -1 when not set */ - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_type; - vgmstream->sample_rate = read_32bitLE(offset + 0x0c,streamFile); - if (vgmstream->sample_rate == 16000) - vgmstream->sample_rate = 48000; // Grandia HD Collection contains a false sample_rate in header - vgmstream->num_samples = num_samples; - vgmstream->loop_start_sample = loop_start; - vgmstream->loop_end_sample = loop_end; - vgmstream->stream_size = data_size; /* to avoid inflated sizes from fake OggS IO */ - -#ifdef VGM_USE_FFMPEG - { - opus_config cfg = {0}; - - cfg.channels = vgmstream->channels; - cfg.skip = skip; - cfg.sample_rate = vgmstream->sample_rate; - - if (multichannel_offset && vgmstream->channels <= 8) { - int i; - cfg.stream_count = read_8bit(multichannel_offset + 0x08,streamFile); - cfg.coupled_count = read_8bit(multichannel_offset + 0x09,streamFile); - for (i = 0; i < vgmstream->channels; i++) { - cfg.channel_mapping[i] = read_8bit(multichannel_offset + 0x0a + i,streamFile); - } - } - - vgmstream->codec_data = init_ffmpeg_switch_opus_config(streamFile, start_offset,data_size, &cfg); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data); - - if (vgmstream->num_samples == 0) { - vgmstream->num_samples = switch_opus_get_samples(start_offset, data_size, streamFile) - skip; - } - } -#else - goto fail; -#endif - - if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - - -/* standard Switch Opus, Nintendo header + raw data (generated by opus_test.c?) [Lego City Undercover (Switch)] */ -VGMSTREAM * init_vgmstream_opus_std(STREAMFILE *streamFile) { - STREAMFILE * PSIFile = NULL; - off_t offset; - int num_samples, loop_start, loop_end; - - /* checks */ - if (!check_extensions(streamFile,"opus,lopus")) - goto fail; - - offset = 0x00; - - /* BlazBlue: Cross Tag Battle (Switch) PSI Metadata for corresponding Opus */ - /* Maybe future Arc System Works games will use this too? */ - PSIFile = open_streamfile_by_ext(streamFile, "psi"); - if (PSIFile) { - num_samples = read_32bitLE(0x8C, PSIFile); - loop_start = read_32bitLE(0x84, PSIFile); - loop_end = read_32bitLE(0x88, PSIFile); - close_streamfile(PSIFile); - } - else { - num_samples = 0; - loop_start = 0; - loop_end = 0; - } - - return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end); -fail: - return NULL; -} - -/* Nippon1 variation [Disgaea 5 (Switch)] */ -VGMSTREAM * init_vgmstream_opus_n1(STREAMFILE *streamFile) { - off_t offset; - int num_samples, loop_start, loop_end; - - /* checks */ - if ( !check_extensions(streamFile,"opus,lopus")) - goto fail; - if (!((read_32bitBE(0x04,streamFile) == 0x00000000 && read_32bitBE(0x0c,streamFile) == 0x00000000) || - (read_32bitBE(0x04,streamFile) == 0xFFFFFFFF && read_32bitBE(0x0c,streamFile) == 0xFFFFFFFF))) - goto fail; - - offset = 0x10; - num_samples = 0; - loop_start = read_32bitLE(0x00,streamFile); - loop_end = read_32bitLE(0x08,streamFile); - - return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end); -fail: - return NULL; -} - -/* Capcom variation [Ultra Street Fighter II (Switch), Resident Evil: Revelations (Switch)] */ -VGMSTREAM * init_vgmstream_opus_capcom(STREAMFILE *streamFile) { - VGMSTREAM *vgmstream = NULL; - off_t offset; - int num_samples, loop_start, loop_end; - int channel_count; - - /* checks */ - if ( !check_extensions(streamFile,"opus,lopus")) - goto fail; - - channel_count = read_32bitLE(0x04,streamFile); - if (channel_count != 1 && channel_count != 2 && channel_count != 6) - goto fail; /* unknown stream layout */ - - num_samples = read_32bitLE(0x00,streamFile); - /* 0x04: channels, >2 uses interleaved streams (2ch+2ch+2ch) */ - loop_start = read_32bitLE(0x08,streamFile); - loop_end = read_32bitLE(0x0c,streamFile); - /* 0x10: frame size (with extra data) */ - /* 0x14: extra chunk count */ - /* 0x18: null */ - offset = read_32bitLE(0x1c,streamFile); - /* 0x20-8: config? (0x0077C102 04000000 E107070C) */ - /* 0x2c: some size? */ - /* 0x30+: extra chunks (0x00: 0x7f, 0x04: num_sample), alt loop starts/regions? */ - - if (channel_count == 6) { - /* 2ch multistream hacky-hacks, don't try this at home. We'll end up with: - * main vgmstream > N vgmstream layers > substream IO deinterleaver > opus meta > Opus IO transmogrifier (phew) */ - layered_layout_data* data = NULL; - int layers = channel_count / 2; - int i; - int loop_flag = (loop_end > 0); - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->layout_type = layout_layered; - - /* init layout */ - data = init_layout_layered(layers); - if (!data) goto fail; - vgmstream->layout_data = data; - - /* open each layer subfile */ - for (i = 0; i < layers; i++) { - STREAMFILE* temp_streamFile = setup_opus_interleave_streamfile(streamFile, offset+0x28*i, layers); - if (!temp_streamFile) goto fail; - - data->layers[i] = init_vgmstream_opus(temp_streamFile, meta_OPUS, 0x00, num_samples,loop_start,loop_end); - close_streamfile(temp_streamFile); - if (!data->layers[i]) goto fail; - } - - /* setup layered VGMSTREAMs */ - if (!setup_layout_layered(data)) - goto fail; - - vgmstream->sample_rate = data->layers[0]->sample_rate; - vgmstream->num_samples = data->layers[0]->num_samples; - vgmstream->loop_start_sample = data->layers[0]->loop_start_sample; - vgmstream->loop_end_sample = data->layers[0]->loop_end_sample; - vgmstream->meta_type = meta_OPUS; - vgmstream->coding_type = data->layers[0]->coding_type; - - return vgmstream; - } - else { - return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end); - } - - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -/* Procyon Studio variation [Xenoblade Chronicles 2 (Switch)] */ -VGMSTREAM * init_vgmstream_opus_nop(STREAMFILE *streamFile) { - off_t offset; - int num_samples, loop_start = 0, loop_end = 0, loop_flag; - - /* checks */ - if (!check_extensions(streamFile,"nop")) - goto fail; - if (read_32bitBE(0x00, streamFile) != 0x73616466 || /* "sadf" */ - read_32bitBE(0x08, streamFile) != 0x6f707573) /* "opus" */ - goto fail; - - offset = read_32bitLE(0x1c, streamFile); - num_samples = read_32bitLE(0x28, streamFile); - loop_flag = read_8bit(0x19, streamFile); - if (loop_flag) { - loop_start = read_32bitLE(0x2c, streamFile); - loop_end = read_32bitLE(0x30, streamFile); - } - - return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end); -fail: - return NULL; -} - -/* Shin'en variation [Fast RMX (Switch)] */ -VGMSTREAM * init_vgmstream_opus_shinen(STREAMFILE *streamFile) { - off_t offset = 0; - int num_samples, loop_start, loop_end; - - /* checks */ - if ( !check_extensions(streamFile,"opus,lopus")) - goto fail; - if (read_32bitBE(0x08,streamFile) != 0x01000080) - goto fail; - - offset = 0x08; - num_samples = 0; - loop_start = read_32bitLE(0x00,streamFile); - loop_end = read_32bitLE(0x04,streamFile); /* 0 if no loop */ - - if (loop_start > loop_end) - goto fail; /* just in case */ - - return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end); -fail: - return NULL; -} - -/* Bandai Namco Opus (found in NUS3Banks) [Taiko no Tatsujin: Nintendo Switch Version!] */ -VGMSTREAM * init_vgmstream_opus_nus3(STREAMFILE *streamFile) { - off_t offset = 0; - int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag; - - /* checks */ - /* .opus: header ID (they only exist inside .nus3bank) */ - if (!check_extensions(streamFile, "opus,lopus")) - goto fail; - if (read_32bitBE(0x00, streamFile) != 0x4F505553) /* "OPUS" */ - goto fail; - - /* Here's an interesting quirk, OPUS header contains big endian values - while the Nintendo Opus header and data that follows remain little endian as usual */ - offset = read_32bitBE(0x20, streamFile); - num_samples = read_32bitBE(0x08, streamFile); - - /* Check if there's a loop end value to determine loop_flag*/ - loop_flag = read_32bitBE(0x18, streamFile); - if (loop_flag) { - loop_start = read_32bitBE(0x14, streamFile); - loop_end = read_32bitBE(0x18, streamFile); - } - - return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end); -fail: - return NULL; -} - -/* Nippon Ichi SPS wrapper (non-segmented) [Ys VIII: Lacrimosa of Dana (Switch)] */ -VGMSTREAM * init_vgmstream_opus_sps_n1(STREAMFILE *streamFile) { - off_t offset; - int num_samples, loop_start = 0, loop_end = 0, loop_flag; - - /* checks */ - /* .sps: Labyrinth of Refrain - Coven of Dusk (Switch) - * .nlsd: Disgaea Refine (Switch), Ys VIII (Switch) */ - if (!check_extensions(streamFile, "sps,nlsd")) - goto fail; - if (read_32bitBE(0x00, streamFile) != 0x09000000) /* file type (see other N1 SPS) */ - goto fail; - - offset = 0x1C; - num_samples = read_32bitLE(0x0C, streamFile); - - /* sections num_samples (remnant of segmented opus_sps_n1): - * 0x10: intro, 0x14: loop, 0x18: end (all must add up to num_samples) */ - loop_flag = read_32bitLE(0x18, streamFile); /* with loop disabled only loop section has samples */ - if (loop_flag) { - loop_start = read_32bitLE(0x10, streamFile); - loop_end = loop_start + read_32bitLE(0x14, streamFile); - } - - return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end); -fail: - return NULL; -} - -/* AQUASTYLE wrapper [Touhou Genso Wanderer -Reloaded- (Switch)] */ -VGMSTREAM * init_vgmstream_opus_opusx(STREAMFILE *streamFile) { - off_t offset; - int num_samples, loop_start = 0, loop_end = 0; - float modifier; - - /* checks */ - if (!check_extensions(streamFile, "opusx")) - goto fail; - if (read_32bitBE(0x00, streamFile) != 0x4F505553) /* "OPUS" */ - goto fail; - - offset = 0x10; - /* values are for the original 44100 files, but Opus resamples to 48000 */ - modifier = 48000.0f / 44100.0f; - num_samples = 0;//read_32bitLE(0x04, streamFile) * modifier; /* better use calc'd num_samples */ - loop_start = read_32bitLE(0x08, streamFile) * modifier; - loop_end = read_32bitLE(0x0c, streamFile) * modifier; - - /* resampling calcs are slighly off and may to over num_samples, but by removing delay seems ok */ - if (loop_start >= 120) { - loop_start -= 128; - loop_end -= 128; - } - else { - loop_end = 0; - } - - return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end); -fail: - return NULL; -} - -/* Prototype variation [Clannad (Switch)] */ -VGMSTREAM * init_vgmstream_opus_prototype(STREAMFILE *streamFile) { - off_t offset = 0; - int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag; - - /* checks */ - if (!check_extensions(streamFile, "opus,lopus")) - goto fail; - if (read_32bitBE(0x00, streamFile) != 0x4F505553 || /* "OPUS" */ - read_32bitBE(0x18, streamFile) != 0x01000080) - goto fail; - - offset = 0x18; - num_samples = read_32bitLE(0x08, streamFile); - - /* Check if there's a loop end value to determine loop_flag*/ - loop_flag = read_32bitLE(0x10, streamFile); - if (loop_flag) { - loop_start = read_32bitLE(0x0C, streamFile); - loop_end = read_32bitLE(0x10, streamFile); - } - - return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end); -fail: - return NULL; -} - -/* Edelweiss variation [Astebreed (Switch)] */ -VGMSTREAM * init_vgmstream_opus_opusnx(STREAMFILE *streamFile) { - off_t offset = 0; - int num_samples = 0, loop_start = 0, loop_end = 0; - - /* checks */ - if (!check_extensions(streamFile, "opus,lopus")) - goto fail; - if (read_64bitBE(0x00, streamFile) != 0x4F5055534E580000) /* "OPUSNX\0\0" */ - goto fail; - - offset = 0x10; - num_samples = 0; //read_32bitLE(0x08, streamFile); /* samples with encoder delay */ - if (read_32bitLE(0x0c, streamFile) != 0) - goto fail; - - return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end); -fail: - return NULL; -} - -/* Square Enix variation [Dragon Quest I-III (Switch)] */ -VGMSTREAM * init_vgmstream_opus_sqex(STREAMFILE *streamFile) { - off_t offset = 0; - int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag; - - /* checks */ - if (!check_extensions(streamFile, "opus,lopus")) - goto fail; - if (read_64bitBE(0x00, streamFile) != 0x0100000002000000) - goto fail; - - offset = read_32bitLE(0x0C, streamFile); - num_samples = read_32bitLE(0x1C, streamFile); - - /* Check if there's a loop end value to determine loop_flag*/ - loop_flag = read_32bitLE(0x18, streamFile); - if (loop_flag) { - loop_start = read_32bitLE(0x14, streamFile); - loop_end = read_32bitLE(0x18, streamFile); - } - - return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end); -fail: - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" +#include "../layout/layout.h" +#include "opus_interleave_streamfile.h" + +/* Nintendo OPUS - from Switch games, including header variations (not the same as Ogg Opus) */ + +static VGMSTREAM * init_vgmstream_opus(STREAMFILE *streamFile, meta_t meta_type, off_t offset, int32_t num_samples, int32_t loop_start, int32_t loop_end) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag = 0, channel_count; + off_t data_offset, multichannel_offset = 0; + size_t data_size, skip = 0; + + + if ((uint32_t)read_32bitLE(offset + 0x00,streamFile) != 0x80000001) + goto fail; + + channel_count = read_8bit(offset + 0x09, streamFile); + /* 0x0a: packet size if CBR, 0 if VBR */ + data_offset = offset + read_32bitLE(offset + 0x10, streamFile); + skip = read_16bitLE(offset + 0x1c, streamFile); + /* 0x1e: ? (seen in Lego Movie 2 (Switch)) */ + + /* recent >2ch info [Clannad (Switch)] */ + if ((uint32_t)read_32bitLE(offset + 0x20, streamFile) == 0x80000005) { + multichannel_offset = offset + 0x20; + } + + if ((uint32_t)read_32bitLE(data_offset, streamFile) != 0x80000004) + goto fail; + + data_size = read_32bitLE(data_offset + 0x04, streamFile); + + start_offset = data_offset + 0x08; + loop_flag = (loop_end > 0); /* -1 when not set */ + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_type; + vgmstream->sample_rate = read_32bitLE(offset + 0x0c,streamFile); + if (vgmstream->sample_rate == 16000) + vgmstream->sample_rate = 48000; // Grandia HD Collection contains a false sample_rate in header + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + vgmstream->stream_size = data_size; /* to avoid inflated sizes from fake OggS IO */ + +#ifdef VGM_USE_FFMPEG + { + opus_config cfg = {0}; + + cfg.channels = vgmstream->channels; + cfg.skip = skip; + cfg.sample_rate = vgmstream->sample_rate; + + if (multichannel_offset && vgmstream->channels <= 8) { + int i; + cfg.stream_count = read_8bit(multichannel_offset + 0x08,streamFile); + cfg.coupled_count = read_8bit(multichannel_offset + 0x09,streamFile); + for (i = 0; i < vgmstream->channels; i++) { + cfg.channel_mapping[i] = read_8bit(multichannel_offset + 0x0a + i,streamFile); + } + } + + vgmstream->codec_data = init_ffmpeg_switch_opus_config(streamFile, start_offset,data_size, &cfg); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data); + + if (vgmstream->num_samples == 0) { + vgmstream->num_samples = switch_opus_get_samples(start_offset, data_size, streamFile) - skip; + } + } +#else + goto fail; +#endif + + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + + +/* standard Switch Opus, Nintendo header + raw data (generated by opus_test.c?) [Lego City Undercover (Switch)] */ +VGMSTREAM * init_vgmstream_opus_std(STREAMFILE *streamFile) { + STREAMFILE * PSIFile = NULL; + off_t offset; + int num_samples, loop_start, loop_end; + + /* checks */ + if (!check_extensions(streamFile,"opus,lopus")) + goto fail; + + offset = 0x00; + + /* BlazBlue: Cross Tag Battle (Switch) PSI Metadata for corresponding Opus */ + /* Maybe future Arc System Works games will use this too? */ + PSIFile = open_streamfile_by_ext(streamFile, "psi"); + if (PSIFile) { + num_samples = read_32bitLE(0x8C, PSIFile); + loop_start = read_32bitLE(0x84, PSIFile); + loop_end = read_32bitLE(0x88, PSIFile); + close_streamfile(PSIFile); + } + else { + num_samples = 0; + loop_start = 0; + loop_end = 0; + } + + return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end); +fail: + return NULL; +} + +/* Nippon1 variation [Disgaea 5 (Switch)] */ +VGMSTREAM * init_vgmstream_opus_n1(STREAMFILE *streamFile) { + off_t offset; + int num_samples, loop_start, loop_end; + + /* checks */ + if ( !check_extensions(streamFile,"opus,lopus")) + goto fail; + if (!((read_32bitBE(0x04,streamFile) == 0x00000000 && read_32bitBE(0x0c,streamFile) == 0x00000000) || + (read_32bitBE(0x04,streamFile) == 0xFFFFFFFF && read_32bitBE(0x0c,streamFile) == 0xFFFFFFFF))) + goto fail; + + offset = 0x10; + num_samples = 0; + loop_start = read_32bitLE(0x00,streamFile); + loop_end = read_32bitLE(0x08,streamFile); + + return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end); +fail: + return NULL; +} + +/* Capcom variation [Ultra Street Fighter II (Switch), Resident Evil: Revelations (Switch)] */ +VGMSTREAM * init_vgmstream_opus_capcom(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + off_t offset; + int num_samples, loop_start, loop_end; + int channel_count; + + /* checks */ + if ( !check_extensions(streamFile,"opus,lopus")) + goto fail; + + channel_count = read_32bitLE(0x04,streamFile); + if (channel_count != 1 && channel_count != 2 && channel_count != 6) + goto fail; /* unknown stream layout */ + + num_samples = read_32bitLE(0x00,streamFile); + /* 0x04: channels, >2 uses interleaved streams (2ch+2ch+2ch) */ + loop_start = read_32bitLE(0x08,streamFile); + loop_end = read_32bitLE(0x0c,streamFile); + /* 0x10: frame size (with extra data) */ + /* 0x14: extra chunk count */ + /* 0x18: null */ + offset = read_32bitLE(0x1c,streamFile); + /* 0x20-8: config? (0x0077C102 04000000 E107070C) */ + /* 0x2c: some size? */ + /* 0x30+: extra chunks (0x00: 0x7f, 0x04: num_sample), alt loop starts/regions? */ + + if (channel_count == 6) { + /* 2ch multistream hacky-hacks, don't try this at home. We'll end up with: + * main vgmstream > N vgmstream layers > substream IO deinterleaver > opus meta > Opus IO transmogrifier (phew) */ + layered_layout_data* data = NULL; + int layers = channel_count / 2; + int i; + int loop_flag = (loop_end > 0); + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->layout_type = layout_layered; + + /* init layout */ + data = init_layout_layered(layers); + if (!data) goto fail; + vgmstream->layout_data = data; + + /* open each layer subfile */ + for (i = 0; i < layers; i++) { + STREAMFILE* temp_streamFile = setup_opus_interleave_streamfile(streamFile, offset+0x28*i, layers); + if (!temp_streamFile) goto fail; + + data->layers[i] = init_vgmstream_opus(temp_streamFile, meta_OPUS, 0x00, num_samples,loop_start,loop_end); + close_streamfile(temp_streamFile); + if (!data->layers[i]) goto fail; + } + + /* setup layered VGMSTREAMs */ + if (!setup_layout_layered(data)) + goto fail; + + vgmstream->sample_rate = data->layers[0]->sample_rate; + vgmstream->num_samples = data->layers[0]->num_samples; + vgmstream->loop_start_sample = data->layers[0]->loop_start_sample; + vgmstream->loop_end_sample = data->layers[0]->loop_end_sample; + vgmstream->meta_type = meta_OPUS; + vgmstream->coding_type = data->layers[0]->coding_type; + + return vgmstream; + } + else { + return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end); + } + + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* Procyon Studio variation [Xenoblade Chronicles 2 (Switch)] */ +VGMSTREAM * init_vgmstream_opus_nop(STREAMFILE *streamFile) { + off_t offset; + int num_samples, loop_start = 0, loop_end = 0, loop_flag; + + /* checks */ + if (!check_extensions(streamFile,"nop")) + goto fail; + if (read_32bitBE(0x00, streamFile) != 0x73616466 || /* "sadf" */ + read_32bitBE(0x08, streamFile) != 0x6f707573) /* "opus" */ + goto fail; + + offset = read_32bitLE(0x1c, streamFile); + num_samples = read_32bitLE(0x28, streamFile); + loop_flag = read_8bit(0x19, streamFile); + if (loop_flag) { + loop_start = read_32bitLE(0x2c, streamFile); + loop_end = read_32bitLE(0x30, streamFile); + } + + return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end); +fail: + return NULL; +} + +/* Shin'en variation [Fast RMX (Switch)] */ +VGMSTREAM * init_vgmstream_opus_shinen(STREAMFILE *streamFile) { + off_t offset = 0; + int num_samples, loop_start, loop_end; + + /* checks */ + if ( !check_extensions(streamFile,"opus,lopus")) + goto fail; + if (read_32bitBE(0x08,streamFile) != 0x01000080) + goto fail; + + offset = 0x08; + num_samples = 0; + loop_start = read_32bitLE(0x00,streamFile); + loop_end = read_32bitLE(0x04,streamFile); /* 0 if no loop */ + + if (loop_start > loop_end) + goto fail; /* just in case */ + + return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end); +fail: + return NULL; +} + +/* Bandai Namco Opus (found in NUS3Banks) [Taiko no Tatsujin: Nintendo Switch Version!] */ +VGMSTREAM * init_vgmstream_opus_nus3(STREAMFILE *streamFile) { + off_t offset = 0; + int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag; + + /* checks */ + /* .opus: header ID (they only exist inside .nus3bank) */ + if (!check_extensions(streamFile, "opus,lopus")) + goto fail; + if (read_32bitBE(0x00, streamFile) != 0x4F505553) /* "OPUS" */ + goto fail; + + /* Here's an interesting quirk, OPUS header contains big endian values + while the Nintendo Opus header and data that follows remain little endian as usual */ + offset = read_32bitBE(0x20, streamFile); + num_samples = read_32bitBE(0x08, streamFile); + + /* Check if there's a loop end value to determine loop_flag*/ + loop_flag = read_32bitBE(0x18, streamFile); + if (loop_flag) { + loop_start = read_32bitBE(0x14, streamFile); + loop_end = read_32bitBE(0x18, streamFile); + } + + return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end); +fail: + return NULL; +} + +/* Nippon Ichi SPS wrapper (non-segmented) [Ys VIII: Lacrimosa of Dana (Switch)] */ +VGMSTREAM * init_vgmstream_opus_sps_n1(STREAMFILE *streamFile) { + off_t offset; + int num_samples, loop_start = 0, loop_end = 0, loop_flag; + + /* checks */ + /* .sps: Labyrinth of Refrain - Coven of Dusk (Switch) + * .nlsd: Disgaea Refine (Switch), Ys VIII (Switch) */ + if (!check_extensions(streamFile, "sps,nlsd")) + goto fail; + if (read_32bitBE(0x00, streamFile) != 0x09000000) /* file type (see other N1 SPS) */ + goto fail; + + offset = 0x1C; + num_samples = read_32bitLE(0x0C, streamFile); + + /* sections num_samples (remnant of segmented opus_sps_n1): + * 0x10: intro, 0x14: loop, 0x18: end (all must add up to num_samples) */ + loop_flag = read_32bitLE(0x18, streamFile); /* with loop disabled only loop section has samples */ + if (loop_flag) { + loop_start = read_32bitLE(0x10, streamFile); + loop_end = loop_start + read_32bitLE(0x14, streamFile); + } + + return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end); +fail: + return NULL; +} + +/* AQUASTYLE wrapper [Touhou Genso Wanderer -Reloaded- (Switch)] */ +VGMSTREAM * init_vgmstream_opus_opusx(STREAMFILE *streamFile) { + off_t offset; + int num_samples, loop_start = 0, loop_end = 0; + float modifier; + + /* checks */ + if (!check_extensions(streamFile, "opusx")) + goto fail; + if (read_32bitBE(0x00, streamFile) != 0x4F505553) /* "OPUS" */ + goto fail; + + offset = 0x10; + /* values are for the original 44100 files, but Opus resamples to 48000 */ + modifier = 48000.0f / 44100.0f; + num_samples = 0;//read_32bitLE(0x04, streamFile) * modifier; /* better use calc'd num_samples */ + loop_start = read_32bitLE(0x08, streamFile) * modifier; + loop_end = read_32bitLE(0x0c, streamFile) * modifier; + + /* resampling calcs are slighly off and may to over num_samples, but by removing delay seems ok */ + if (loop_start >= 120) { + loop_start -= 128; + loop_end -= 128; + } + else { + loop_end = 0; + } + + return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end); +fail: + return NULL; +} + +/* Prototype variation [Clannad (Switch)] */ +VGMSTREAM * init_vgmstream_opus_prototype(STREAMFILE *streamFile) { + off_t offset = 0; + int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag; + + /* checks */ + if (!check_extensions(streamFile, "opus,lopus")) + goto fail; + if (read_32bitBE(0x00, streamFile) != 0x4F505553 || /* "OPUS" */ + read_32bitBE(0x18, streamFile) != 0x01000080) + goto fail; + + offset = 0x18; + num_samples = read_32bitLE(0x08, streamFile); + + /* Check if there's a loop end value to determine loop_flag*/ + loop_flag = read_32bitLE(0x10, streamFile); + if (loop_flag) { + loop_start = read_32bitLE(0x0C, streamFile); + loop_end = read_32bitLE(0x10, streamFile); + } + + return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end); +fail: + return NULL; +} + +/* Edelweiss variation [Astebreed (Switch)] */ +VGMSTREAM * init_vgmstream_opus_opusnx(STREAMFILE *streamFile) { + off_t offset = 0; + int num_samples = 0, loop_start = 0, loop_end = 0; + + /* checks */ + if (!check_extensions(streamFile, "opus,lopus")) + goto fail; + if (read_64bitBE(0x00, streamFile) != 0x4F5055534E580000) /* "OPUSNX\0\0" */ + goto fail; + + offset = 0x10; + num_samples = 0; //read_32bitLE(0x08, streamFile); /* samples with encoder delay */ + if (read_32bitLE(0x0c, streamFile) != 0) + goto fail; + + return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end); +fail: + return NULL; +} + +/* Square Enix variation [Dragon Quest I-III (Switch)] */ +VGMSTREAM * init_vgmstream_opus_sqex(STREAMFILE *streamFile) { + off_t offset = 0; + int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag; + + /* checks */ + if (!check_extensions(streamFile, "opus,lopus")) + goto fail; + if (read_64bitBE(0x00, streamFile) != 0x0100000002000000) + goto fail; + + offset = read_32bitLE(0x0C, streamFile); + num_samples = read_32bitLE(0x1C, streamFile); + + /* Check if there's a loop end value to determine loop_flag*/ + loop_flag = read_32bitLE(0x18, streamFile); + if (loop_flag) { + loop_start = read_32bitLE(0x14, streamFile); + loop_end = read_32bitLE(0x18, streamFile); + } + + return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end); +fail: + return NULL; +} From cf53f38c536e34268b06d258635b574237776445 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 2 Feb 2020 18:45:21 +0100 Subject: [PATCH 02/11] Fix looping .bgm Ogg Opus [Utawarerumono: Mask of Truth (PC)] --- src/meta/ogg_opus.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/meta/ogg_opus.c b/src/meta/ogg_opus.c index 29717d43..521b8acc 100644 --- a/src/meta/ogg_opus.c +++ b/src/meta/ogg_opus.c @@ -16,8 +16,9 @@ VGMSTREAM * init_vgmstream_ogg_opus(STREAMFILE *streamFile) { /* checks */ /* .opus: standard, .lopus: fake extension for plugins - * .ogg: less common, .logg: same */ - if (!check_extensions(streamFile, "opus,lopus,ogg,logg")) + * .ogg: less common, .logg: same + * .bgm: Utawarerumono: Mask of Truth (PC) */ + if (!check_extensions(streamFile, "opus,lopus,ogg,logg,bgm")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x4F676753) /* "OggS" */ goto fail; @@ -78,6 +79,13 @@ VGMSTREAM * init_vgmstream_ogg_opus(STREAMFILE *streamFile) { else if (strstr(user_comment,"TITLE=")==user_comment) { /* for detection */ has_title = 1; } + else if (strstr(user_comment,"LoopStart=")==user_comment) { /* Utawarerumono: Mask of Truth (PC) */ + loop_start= atol(strrchr(user_comment,'=')+1); + loop_flag = (loop_start >= 0); + } + else if (strstr(user_comment,"LoopEnd=")==user_comment) { /* LoopStart pair */ + loop_end = atol(strrchr(user_comment,'=')+1); + } //;VGM_LOG("OggOpus: user_comment=%s\n", user_comment); From 92c3a9ec942b51439344ff1c3ce5a3590131fab5 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 2 Feb 2020 18:45:34 +0100 Subject: [PATCH 03/11] Add N1 .at9 NXOpus [void tRrLM(); //Void Terrarium (Switch)] --- src/meta/opus.c | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/meta/opus.c b/src/meta/opus.c index 4611e07c..ac07973d 100644 --- a/src/meta/opus.c +++ b/src/meta/opus.c @@ -308,22 +308,37 @@ VGMSTREAM * init_vgmstream_opus_sps_n1(STREAMFILE *streamFile) { int num_samples, loop_start = 0, loop_end = 0, loop_flag; /* checks */ - /* .sps: Labyrinth of Refrain - Coven of Dusk (Switch) - * .nlsd: Disgaea Refine (Switch), Ys VIII (Switch) */ - if (!check_extensions(streamFile, "sps,nlsd")) + /* .sps: Labyrinth of Refrain: Coven of Dusk (Switch) + * .nlsd: Disgaea Refine (Switch), Ys VIII (Switch) + * .at9: void tRrLM(); //Void Terrarium (Switch) */ + if (!check_extensions(streamFile, "sps,nlsd,at9")) goto fail; if (read_32bitBE(0x00, streamFile) != 0x09000000) /* file type (see other N1 SPS) */ goto fail; - offset = 0x1C; num_samples = read_32bitLE(0x0C, streamFile); - /* sections num_samples (remnant of segmented opus_sps_n1): - * 0x10: intro, 0x14: loop, 0x18: end (all must add up to num_samples) */ - loop_flag = read_32bitLE(0x18, streamFile); /* with loop disabled only loop section has samples */ - if (loop_flag) { + if ( read_32bitLE(0x1c, streamFile) == 0x01000080) { + offset = 0x1C; + + /* older games loop section (remnant of segmented opus_sps_n1): */ + loop_start = read_32bitLE(0x10, streamFile); /* intro samples */ + loop_end = loop_start + read_32bitLE(0x14, streamFile); /* loop samples */ + /* 0x18: end samples (all must add up to num_samples) */ + loop_flag = read_32bitLE(0x18, streamFile); /* with loop disabled only loop_end has a value */ + } + else { + offset = 0x18; + + /* newer games loop section: */ loop_start = read_32bitLE(0x10, streamFile); - loop_end = loop_start + read_32bitLE(0x14, streamFile); + loop_end = read_32bitLE(0x14, streamFile); + loop_flag = loop_start != loop_end; /* with loop disabled start and end are the same as num samples */ + } + + if (!loop_flag) { + loop_start = 0; + loop_end = 0; } return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end); From 657202380b2589fcafc12d65ca903ae1d93a2e71 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 2 Feb 2020 18:45:45 +0100 Subject: [PATCH 04/11] Fix some ZSND .zsd [X-Men: The Official Game (Xbox), BMX XXX (Xbox)] --- src/formats.c | 2 +- src/meta/zsnd.c | 64 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/formats.c b/src/formats.c index bdf27105..962f876f 100644 --- a/src/formats.c +++ b/src/formats.c @@ -1234,7 +1234,7 @@ static const meta_info meta_info_list[] = { {meta_XPCM, "Circus XPCM header"}, {meta_MSF_TAMASOFT, "Tama-Soft MSF header"}, {meta_XPS_DAT, "From Software .XPS+DAT header"}, - {meta_ZSND, "Vicarious Visions ZSND header"}, + {meta_ZSND, "Z-Axis ZSND header"}, {meta_DSP_ADPY, "AQUASTYLE ADPY header"}, {meta_DSP_ADPX, "AQUASTYLE ADPX header"}, {meta_OGG_OPUS, "Ogg Opus header"}, diff --git a/src/meta/zsnd.c b/src/meta/zsnd.c index 8c7757d5..72c990ae 100644 --- a/src/meta/zsnd.c +++ b/src/meta/zsnd.c @@ -3,13 +3,13 @@ #include "zsnd_streamfile.h" -/* ZSND - Vicarious Visions games [X-Men Legends II (multi), Marvel Ultimate Alliance (multi)] */ +/* ZSND - Z-Axis/Vicarious Visions games [X-Men Legends II (multi), Marvel Ultimate Alliance (multi)] */ VGMSTREAM * init_vgmstream_zsnd(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; STREAMFILE *temp_streamFile = NULL; off_t start_offset, name_offset; size_t stream_size, name_size; - int loop_flag, channel_count, sample_rate, layers; + int loop_flag, channel_count, sample_rate, layers, layers2 = 0; uint32_t codec; int total_subsongs, target_subsong = streamFile->stream_index; int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; @@ -45,7 +45,7 @@ VGMSTREAM * init_vgmstream_zsnd(STREAMFILE *streamFile) { off_t header2_offset, header3_offset; int table2_entries, table3_entries; off_t table2_body, table3_body; - int is_compact, i; + int is_v1, i; /* multiple config tables: @@ -68,11 +68,16 @@ VGMSTREAM * init_vgmstream_zsnd(STREAMFILE *streamFile) { * table1 may have more entries than table2/3, and sometimes isn't set */ - /* 'compact' mode has no table heads, rare [Aggresive Inline (Xbox)] + /* V1 has no table heads, rare [Aggresive Inline (Xbox)] * no apparent flag but we can test if table heads offsets appear */ - is_compact = read_32bit(0x14,streamFile) > read_32bit(0x18,streamFile); + is_v1 = read_32bit(0x14,streamFile) <= read_32bit(0x1c,streamFile) && + read_32bit(0x1c,streamFile) <= read_32bit(0x24,streamFile) && + read_32bit(0x24,streamFile) <= read_32bit(0x2c,streamFile) && + read_32bit(0x2c,streamFile) <= read_32bit(0x34,streamFile) && + read_32bit(0x34,streamFile) <= read_32bit(0x3c,streamFile) && + read_32bit(0x3c,streamFile) <= read_32bit(0x44,streamFile); - if (!is_compact) { + if (!is_v1) { table2_entries = read_32bit(0x1c,streamFile); table2_body = read_32bit(0x24,streamFile); @@ -108,7 +113,7 @@ VGMSTREAM * init_vgmstream_zsnd(STREAMFILE *streamFile) { break; case 0x58424F58: { /* "XBOX" */ - size_t entry2_size = is_compact ? 0x14 : 0x1c; + size_t entry2_size = is_v1 || check_extensions(streamFile, "zsd") ? 0x14 : 0x1c; /* BMX has unordered stream headers, and not every stream has a header */ header2_offset = 0; @@ -129,30 +134,51 @@ VGMSTREAM * init_vgmstream_zsnd(STREAMFILE *streamFile) { sample_rate = read_32bit(header2_offset + 0x04,streamFile); } else { - /* defaults to this in cutscene files in BMX with no heads at all, - * but also needs mono for speech files in Aggresive Inline */ - if (is_compact) { - layers = 0x00; - sample_rate = 16000; - } - else { - layers = 0x02; - sample_rate = 44100; - } + layers = 0; + sample_rate = 0; } } else { layers = read_16bit(header2_offset + 0x02,streamFile); sample_rate = read_32bit(header2_offset + 0x04,streamFile); + if (entry2_size > 0x18) { + layers2 = read_32bit(header2_offset + 0x18,streamFile); + } } header3_offset = table3_body + 0x54*(target_subsong-1); start_offset = read_32bit(header3_offset + 0x00,streamFile); stream_size = read_32bit(header3_offset + 0x04,streamFile); + /* 0x08: flags? related to looping? (not channels) */ //loop_end = read_32bit(header3_offset + 0x10,streamFile); name_offset = header3_offset + 0x14; name_size = 0x40; + /* early games sometimes don't seem to have info or headers, not sure how to detect better + * ex. Aggresive Inline speech (1ch) vs music (2ch), or BMX cutscenes (2ch) */ + if (sample_rate == 0) { + int is_music = 0; + if (is_v1) { + char filename[PATH_LIMIT]; + + /* stream length isn't enough */ + get_streamfile_filename(streamFile, filename, sizeof(filename)); + is_music = strcmp(filename, "music.zsd") == 0; + } + else { + is_music = stream_size > 0x20000; + } + + if (is_music) { + layers = 0x02; + sample_rate = 44100; + } + else { + layers = 0x00; + sample_rate = is_v1 ? 16000 : 22050; /* some BMX need 16000 but can't detect? */ + } + } + break; } @@ -209,6 +235,10 @@ VGMSTREAM * init_vgmstream_zsnd(STREAMFILE *streamFile) { goto fail; } + if (layers2) { + channel_count = channel_count * layers2; + } + loop_flag = 0; } From 9b4769c3addc7de9c0d237a2e8de296ae8be6337 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 2 Feb 2020 19:58:09 +0100 Subject: [PATCH 05/11] CRLF to LF --- src/meta/vsv.c | 170 ++++++++++++++++++++++++------------------------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/src/meta/vsv.c b/src/meta/vsv.c index 8240fd28..a95b8a20 100644 --- a/src/meta/vsv.c +++ b/src/meta/vsv.c @@ -1,85 +1,85 @@ -#include "meta.h" -#include "../coding/coding.h" -#include "vsv_streamfile.h" - - -/* .VSV - from Square Enix games [Dawn of Mana: Seiken Densetsu 4 (PS2), Kingdom Hearts Re:Chain of Memories (PS2)] */ -VGMSTREAM * init_vgmstream_vsv(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE *temp_streamFile = NULL; - off_t start_offset; - int loop_flag, channel_count, flags, sample_rate, is_rs; - size_t loop_start, adjust, data_size, interleave; - - - /* checks */ - /* .vsv: extension from internal filenames [KH Re:CoM (PS2), DoM (PS2), KH HD I.5 + II.5 ReMIX (PS4)] - * .psh: fake */ - if (!check_extensions(streamFile, "vsv,psh")) - goto fail; - - /* 0x00(1x4): flags/config? */ - if ((uint8_t)read_8bit(0x03,streamFile) > 0x64) /* possibly volume */ - goto fail; - if ((uint8_t)read_8bit(0x0a,streamFile) != 0) /* not seen */ - goto fail; - - /* Romancing SaGa (PS2) uses an earlier? version, this seems to work */ - is_rs = ((uint16_t)read_16bitLE(0x00,streamFile) == 0); - - start_offset = 0x00; /* correct, but needs some tricks to fix sound (see below) */ - interleave = 0x800; - - adjust = (uint16_t)read_16bitLE(0x04,streamFile); - loop_start = ((uint16_t)read_16bitLE(0x06,streamFile) & 0x7FFF) * interleave; - loop_flag = (uint16_t)read_16bitLE(0x06,streamFile) & 0x8000; /* loop_start != 0 works too, no files loop from beginning to end */ - sample_rate = (uint16_t)read_16bitLE(0x08,streamFile); - flags = (uint8_t)read_8bit (0x0b,streamFile); /* values: 0x01=stereo, 0x10=mono */ - data_size = (uint16_t)read_16bitLE(0x0c,streamFile) * interleave; - /* 0x0e: ? (may be a low-ish value) */ - - channel_count = (flags & 1) ? 2 : 1; - - /* must discard to avoid wrong loops and unwanted data (easier to see in voices) */ - if (!is_rs) { /* RS doesn't do this */ - /* adjust & 0xF800 is unknown (values=0x0000|0x0800|0xF800, can be mono/stereo, loop/no, adjust/no) */ - size_t discard = adjust & 0x07FF; - /* at (file_end - 0x800 + discard) is a 0x03 PS flag to check this (adjust 0 does discard full block) */ - data_size -= (0x800 - discard) * channel_count; - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); - if (!vgmstream) goto fail; - - vgmstream->meta_type = meta_VSV; - vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count); - vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channel_count); - vgmstream->loop_end_sample = vgmstream->num_samples; - - /* these loops are odd, but comparing the audio wave with the OSTs values seem correct */ - if (is_rs) { - vgmstream->loop_start_sample -= ps_bytes_to_samples(channel_count*interleave,channel_count); /* maybe *before* loop block? */ - vgmstream->loop_start_sample -= ps_bytes_to_samples(0x200*channel_count,channel_count); /* maybe default adjust? */ - } - - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = interleave; - - temp_streamFile = setup_vsv_streamfile(streamFile, start_offset, data_size); - if (!temp_streamFile) goto fail; - - if (!vgmstream_open_stream(vgmstream, temp_streamFile, start_offset)) - goto fail; - - close_streamfile(temp_streamFile); - return vgmstream; - -fail: - close_streamfile(temp_streamFile); - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" +#include "vsv_streamfile.h" + + +/* .VSV - from Square Enix games [Dawn of Mana: Seiken Densetsu 4 (PS2), Kingdom Hearts Re:Chain of Memories (PS2)] */ +VGMSTREAM * init_vgmstream_vsv(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + off_t start_offset; + int loop_flag, channel_count, flags, sample_rate, is_rs; + size_t loop_start, adjust, data_size, interleave; + + + /* checks */ + /* .vsv: extension from internal filenames [KH Re:CoM (PS2), DoM (PS2), KH HD I.5 + II.5 ReMIX (PS4)] + * .psh: fake */ + if (!check_extensions(streamFile, "vsv,psh")) + goto fail; + + /* 0x00(1x4): flags/config? */ + if ((uint8_t)read_8bit(0x03,streamFile) > 0x64) /* possibly volume */ + goto fail; + if ((uint8_t)read_8bit(0x0a,streamFile) != 0) /* not seen */ + goto fail; + + /* Romancing SaGa (PS2) uses an earlier? version, this seems to work */ + is_rs = ((uint16_t)read_16bitLE(0x00,streamFile) == 0); + + start_offset = 0x00; /* correct, but needs some tricks to fix sound (see below) */ + interleave = 0x800; + + adjust = (uint16_t)read_16bitLE(0x04,streamFile); + loop_start = ((uint16_t)read_16bitLE(0x06,streamFile) & 0x7FFF) * interleave; + loop_flag = (uint16_t)read_16bitLE(0x06,streamFile) & 0x8000; /* loop_start != 0 works too, no files loop from beginning to end */ + sample_rate = (uint16_t)read_16bitLE(0x08,streamFile); + flags = (uint8_t)read_8bit (0x0b,streamFile); /* values: 0x01=stereo, 0x10=mono */ + data_size = (uint16_t)read_16bitLE(0x0c,streamFile) * interleave; + /* 0x0e: ? (may be a low-ish value) */ + + channel_count = (flags & 1) ? 2 : 1; + + /* must discard to avoid wrong loops and unwanted data (easier to see in voices) */ + if (!is_rs) { /* RS doesn't do this */ + /* adjust & 0xF800 is unknown (values=0x0000|0x0800|0xF800, can be mono/stereo, loop/no, adjust/no) */ + size_t discard = adjust & 0x07FF; + /* at (file_end - 0x800 + discard) is a 0x03 PS flag to check this (adjust 0 does discard full block) */ + data_size -= (0x800 - discard) * channel_count; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_VSV; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count); + vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channel_count); + vgmstream->loop_end_sample = vgmstream->num_samples; + + /* these loops are odd, but comparing the audio wave with the OSTs values seem correct */ + if (is_rs) { + vgmstream->loop_start_sample -= ps_bytes_to_samples(channel_count*interleave,channel_count); /* maybe *before* loop block? */ + vgmstream->loop_start_sample -= ps_bytes_to_samples(0x200*channel_count,channel_count); /* maybe default adjust? */ + } + + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; + + temp_streamFile = setup_vsv_streamfile(streamFile, start_offset, data_size); + if (!temp_streamFile) goto fail; + + if (!vgmstream_open_stream(vgmstream, temp_streamFile, start_offset)) + goto fail; + + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} From a22defe1a96ed1a5dd15d39fe15d1af450fe2d61 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 2 Feb 2020 21:13:16 +0100 Subject: [PATCH 06/11] Clean some IO streamfile code --- src/meta/jstm_streamfile.h | 87 +++++------ src/meta/ogg_vorbis_streamfile.h | 135 +++++++--------- src/meta/riff_ogg_streamfile.h | 257 +++++++++++++++---------------- src/meta/vsv.c | 2 +- src/meta/vsv_streamfile.h | 93 +++++------ 5 files changed, 263 insertions(+), 311 deletions(-) diff --git a/src/meta/jstm_streamfile.h b/src/meta/jstm_streamfile.h index e28e5a6b..1a728939 100644 --- a/src/meta/jstm_streamfile.h +++ b/src/meta/jstm_streamfile.h @@ -1,51 +1,36 @@ -#ifndef _JSTM_STREAMFILE_H_ -#define _JSTM_STREAMFILE_H_ -#include "../streamfile.h" - - -typedef struct { - off_t start_offset; -} jstm_decryption_data; - -static size_t jstm_decryption_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, jstm_decryption_data* data) { - size_t bytes_read; - int i; - - bytes_read = streamfile->read(streamfile, dest, offset, length); - - /* decrypt data (xor) */ - for (i = 0; i < bytes_read; i++) { - if (offset+i >= data->start_offset) { - dest[i] = dest[i] ^ 0x5A; - } - } - - return bytes_read; -} - -static STREAMFILE* setup_jstm_streamfile(STREAMFILE *streamFile, off_t start_offset) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - jstm_decryption_data io_data = {0}; - size_t io_data_size = sizeof(jstm_decryption_data); - - /* setup decryption */ - io_data.start_offset = start_offset; - - - /* setup custom streamfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, jstm_decryption_read,NULL); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - return temp_streamFile; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} - -#endif /* _JSTM_STREAMFILE_H_ */ +#ifndef _JSTM_STREAMFILE_H_ +#define _JSTM_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + off_t start; +} jstm_io_data; + +static size_t jstm_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, jstm_io_data* data) { + int i; + size_t bytes = read_streamfile(dest, offset, length, sf); + + /* decrypt data (xor) */ + for (i = 0; i < bytes; i++) { + if (offset + i >= data->start) { + dest[i] ^= 0x5A; + } + } + + return bytes; +} + +/* decrypts JSTM stream */ +static STREAMFILE* setup_jstm_streamfile(STREAMFILE *sf, off_t start) { + STREAMFILE *new_sf = NULL; + jstm_io_data io_data = {0}; + + io_data.start = start; + + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(jstm_io_data), jstm_io_read, NULL); + return new_sf; +} + +#endif /* _JSTM_STREAMFILE_H_ */ diff --git a/src/meta/ogg_vorbis_streamfile.h b/src/meta/ogg_vorbis_streamfile.h index ca55dcda..c7b1e105 100644 --- a/src/meta/ogg_vorbis_streamfile.h +++ b/src/meta/ogg_vorbis_streamfile.h @@ -1,76 +1,59 @@ -#ifndef _OGG_VORBIS_STREAMFILE_H_ -#define _OGG_VORBIS_STREAMFILE_H_ -#include "../streamfile.h" - - -typedef struct { - int is_encrypted; - uint8_t key[0x100]; - size_t key_len; - int is_nibble_swap; - int is_header_swap; -} ogg_vorbis_io_config_data; - -typedef struct { - /* config */ - ogg_vorbis_io_config_data cfg; -} ogg_vorbis_io_data; - - -static size_t ogg_vorbis_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, ogg_vorbis_io_data* data) { - size_t bytes_read; - int i; - static const uint8_t header_swap[4] = { 0x4F,0x67,0x67,0x53 }; /* "OggS" */ - static const size_t header_size = 0x04; - - bytes_read = streamfile->read(streamfile, dest, offset, length); - - if (data->cfg.is_encrypted) { - for (i = 0; i < bytes_read; i++) { - if (data->cfg.is_header_swap && (offset + i) < header_size) { - dest[i] = header_swap[(offset + i) % header_size]; - } - else { - if (!data->cfg.key_len && !data->cfg.is_nibble_swap) - break; - if (data->cfg.key_len) - dest[i] ^= data->cfg.key[(offset + i) % data->cfg.key_len]; - if (data->cfg.is_nibble_swap) - dest[i] = ((dest[i] << 4) & 0xf0) | ((dest[i] >> 4) & 0x0f); - } - } - } - - return bytes_read; -} - -//todo maybe use generic decryption streamfile -static STREAMFILE* setup_ogg_vorbis_streamfile(STREAMFILE *streamFile, ogg_vorbis_io_config_data cfg) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - ogg_vorbis_io_data io_data = {0}; - size_t io_data_size = sizeof(ogg_vorbis_io_data); - - /* setup decryption */ - io_data.cfg = cfg; /* memcpy */ - - - /* setup custom streamfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - //todo extension .ogg? - - new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, ogg_vorbis_io_read,NULL); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - return temp_streamFile; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} - - -#endif /* _OGG_VORBIS_STREAMFILE_H_ */ +#ifndef _OGG_VORBIS_STREAMFILE_H_ +#define _OGG_VORBIS_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + int is_encrypted; + uint8_t key[0x100]; + size_t key_len; + int is_nibble_swap; + int is_header_swap; +} ogg_vorbis_io_config_data; + +typedef struct { + /* config */ + ogg_vorbis_io_config_data cfg; +} ogg_vorbis_io_data; + + +static size_t ogg_vorbis_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, ogg_vorbis_io_data* data) { + static const uint8_t header_swap[4] = { 0x4F,0x67,0x67,0x53 }; /* "OggS" */ + static const size_t header_size = 0x04; + int i; + size_t bytes = read_streamfile(dest, offset, length, sf); + + if (data->cfg.is_encrypted) { + for (i = 0; i < bytes; i++) { + if (data->cfg.is_header_swap && (offset + i) < header_size) { + dest[i] = header_swap[(offset + i) % header_size]; + } + else { + if (!data->cfg.key_len && !data->cfg.is_nibble_swap) + break; + if (data->cfg.key_len) + dest[i] ^= data->cfg.key[(offset + i) % data->cfg.key_len]; + if (data->cfg.is_nibble_swap) + dest[i] = ((dest[i] << 4) & 0xf0) | ((dest[i] >> 4) & 0x0f); + } + } + } + + return bytes; +} + +//todo maybe use generic decryption streamfile +/* Decrypts Ogg Vorbis streams */ +static STREAMFILE* setup_ogg_vorbis_streamfile(STREAMFILE *sf, ogg_vorbis_io_config_data cfg) { + STREAMFILE *new_sf = NULL; + ogg_vorbis_io_data io_data = {0}; + + io_data.cfg = cfg; /* memcpy */ + + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(ogg_vorbis_io_data), ogg_vorbis_io_read, NULL); + //new_sf = open_fakename_streamfile_f(new_sf, NULL, "ogg"); //todo? + return new_sf; +} + +#endif /* _OGG_VORBIS_STREAMFILE_H_ */ diff --git a/src/meta/riff_ogg_streamfile.h b/src/meta/riff_ogg_streamfile.h index 6c7c599d..35ee7b16 100644 --- a/src/meta/riff_ogg_streamfile.h +++ b/src/meta/riff_ogg_streamfile.h @@ -1,130 +1,127 @@ -#ifndef _RIFF_OGG_STREAMFILE_H_ -#define _RIFF_OGG_STREAMFILE_H_ -#include "../streamfile.h" - -#ifdef VGM_USE_VORBIS -typedef struct { - off_t patch_offset; -} riff_ogg_io_data; - -static size_t riff_ogg_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, riff_ogg_io_data* data) { - size_t bytes_read = streamfile->read(streamfile, dest, offset, length); - - /* has garbage init Oggs pages, patch bad flag */ - if (data->patch_offset && data->patch_offset >= offset && data->patch_offset < offset + bytes_read) { - VGM_ASSERT(dest[data->patch_offset - offset] != 0x02, "RIFF Ogg: bad patch offset at %lx\n", data->patch_offset); - dest[data->patch_offset - offset] = 0x00; - } - - return bytes_read; -} - -static size_t ogg_get_page(uint8_t *buf, size_t bufsize, off_t offset, STREAMFILE *sf) { - size_t segments, bytes, page_size; - int i; - - if (0x1b > bufsize) goto fail; - bytes = read_streamfile(buf, offset, 0x1b, sf); - if (bytes != 0x1b) goto fail; - - segments = get_u8(buf + 0x1a); - if (0x1b + segments > bufsize) goto fail; - - bytes = read_streamfile(buf + 0x1b, offset + 0x1b, segments, sf); - if (bytes != segments) goto fail; - - page_size = 0x1b + segments; - for (i = 0; i < segments; i++) { - page_size += get_u8(buf + 0x1b + i); - } - - return page_size; -fail: - return 0; -} - -/* patches Oggs with weirdness */ -static STREAMFILE* setup_riff_ogg_streamfile(STREAMFILE *sf, off_t start, size_t size) { - off_t patch_offset = 0; - size_t real_size = size; - uint8_t buf[0x1000]; - - - /* initial page flag is repeated and causes glitches in decoders, find bad offset */ - //todo callback could patch on-the-fly by analyzing all "OggS", but is problematic due to arbitrary offsets - { - off_t offset = start; - size_t page_size; - off_t offset_limit = start + size; /* usually in the first 0x3000 but can be +0x100000 */ - //todo this doesn't seem to help much - STREAMFILE *temp_sf = reopen_streamfile(sf, 0x100); /* use small-ish sf to avoid reading the whole thing */ - - /* first page is ok */ - page_size = ogg_get_page(buf, sizeof(buf), offset, temp_sf); - offset += page_size; - - while (offset < offset_limit) { - page_size = ogg_get_page(buf, sizeof(buf), offset, temp_sf); - if (page_size == 0) break; - - if (get_u32be(buf + 0x00) != 0x4f676753) /* "OggS" */ - break; - - if (get_u16be(buf + 0x04) == 0x0002) { /* start page flag */ - //;VGM_ASSERT(patch_offset > 0, "RIFF Ogg: found multiple repeated start pages\n"); - patch_offset = (offset - start) + 0x04 + 0x01; /* clamp'ed */ - break; - } - - offset += page_size; - } - - close_streamfile(temp_sf); - - if (patch_offset == 0) - return NULL; - } - - /* has a bunch of padding(?) pages at the end with no data nor flag that confuse decoders, find actual end */ - { - size_t chunk_size = sizeof(buf); /* not worth testing more */ - size_t max_size = size; - size_t pos; - off_t read_offset = start + size - chunk_size; - - pos = read_streamfile(buf, read_offset, chunk_size, sf); - if (read_offset < 0 || pos <= 0x1a) return NULL; - - pos -= 0x1a; /* at least one OggS page */ - while (pos > 0) { - if (get_u32be(buf + pos + 0x00) == 0x4f676753) { /* "OggS" */ - - if (get_u16be(buf + pos + 0x04) == 0x0004) { /* last page flag is ok */ - real_size = max_size; - break; - } - else { /* last page flag is wrong */ - max_size = size - (chunk_size - pos); /* update size up to this page */ - } - } - pos--; - } - } - - /* actual custom streamfile init */ - { - STREAMFILE *new_sf = NULL; - riff_ogg_io_data io_data = {0}; - - io_data.patch_offset = patch_offset; - - new_sf = open_wrap_streamfile(sf); - new_sf = open_clamp_streamfile_f(new_sf, start, real_size); - new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(riff_ogg_io_data), riff_ogg_io_read, NULL); - return new_sf; - } -} - -#endif /* VGM_USE_VORBIS */ - -#endif /* _RIFF_OGG_STREAMFILE_H_ */ +#ifndef _RIFF_OGG_STREAMFILE_H_ +#define _RIFF_OGG_STREAMFILE_H_ +#include "deblock_streamfile.h" + +typedef struct { + off_t patch_offset; +} riff_ogg_io_data; + +static size_t riff_ogg_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, riff_ogg_io_data* data) { + size_t bytes = read_streamfile(dest, offset, length, sf); + + /* has garbage init Oggs pages, patch bad flag */ + if (data->patch_offset && data->patch_offset >= offset && data->patch_offset < offset + bytes) { + VGM_ASSERT(dest[data->patch_offset - offset] != 0x02, "RIFF Ogg: bad patch offset at %lx\n", data->patch_offset); + dest[data->patch_offset - offset] = 0x00; + } + + return bytes; +} + +static size_t ogg_get_page(uint8_t *buf, size_t bufsize, off_t offset, STREAMFILE *sf) { + size_t segments, bytes, page_size; + int i; + + if (0x1b > bufsize) goto fail; + bytes = read_streamfile(buf, offset, 0x1b, sf); + if (bytes != 0x1b) goto fail; + + segments = get_u8(buf + 0x1a); + if (0x1b + segments > bufsize) goto fail; + + bytes = read_streamfile(buf + 0x1b, offset + 0x1b, segments, sf); + if (bytes != segments) goto fail; + + page_size = 0x1b + segments; + for (i = 0; i < segments; i++) { + page_size += get_u8(buf + 0x1b + i); + } + + return page_size; +fail: + return 0; +} + +/* patches Ogg with weirdness */ +static STREAMFILE* setup_riff_ogg_streamfile(STREAMFILE *sf, off_t start, size_t size) { + off_t patch_offset = 0; + size_t real_size = size; + uint8_t buf[0x1000]; + + + /* initial page flag is repeated and causes glitches in decoders, find bad offset */ + //todo callback could patch on-the-fly by analyzing all "OggS", but is problematic due to arbitrary offsets + { + off_t offset = start; + size_t page_size; + off_t offset_limit = start + size; /* usually in the first 0x3000 but can be +0x100000 */ + //todo this doesn't seem to help much + STREAMFILE *temp_sf = reopen_streamfile(sf, 0x100); /* use small-ish sf to avoid reading the whole thing */ + + /* first page is ok */ + page_size = ogg_get_page(buf, sizeof(buf), offset, temp_sf); + offset += page_size; + + while (offset < offset_limit) { + page_size = ogg_get_page(buf, sizeof(buf), offset, temp_sf); + if (page_size == 0) break; + + if (get_u32be(buf + 0x00) != 0x4f676753) /* "OggS" */ + break; + + if (get_u16be(buf + 0x04) == 0x0002) { /* start page flag */ + //;VGM_ASSERT(patch_offset > 0, "RIFF Ogg: found multiple repeated start pages\n"); + patch_offset = (offset - start) + 0x04 + 0x01; /* clamp'ed */ + break; + } + + offset += page_size; + } + + close_streamfile(temp_sf); + + if (patch_offset == 0) + return NULL; + } + + /* has a bunch of padding(?) pages at the end with no data nor flag that confuse decoders, find actual end */ + { + size_t chunk_size = sizeof(buf); /* not worth testing more */ + size_t max_size = size; + size_t pos; + off_t read_offset = start + size - chunk_size; + + pos = read_streamfile(buf, read_offset, chunk_size, sf); + if (read_offset < 0 || pos <= 0x1a) return NULL; + + pos -= 0x1a; /* at least one OggS page */ + while (pos > 0) { + if (get_u32be(buf + pos + 0x00) == 0x4f676753) { /* "OggS" */ + + if (get_u16be(buf + pos + 0x04) == 0x0004) { /* last page flag is ok */ + real_size = max_size; + break; + } + else { /* last page flag is wrong */ + max_size = size - (chunk_size - pos); /* update size up to this page */ + } + } + pos--; + } + } + + /* actual custom streamfile init */ + { + STREAMFILE *new_sf = NULL; + riff_ogg_io_data io_data = {0}; + + io_data.patch_offset = patch_offset; + + new_sf = open_wrap_streamfile(sf); + new_sf = open_clamp_streamfile_f(new_sf, start, real_size); + new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(riff_ogg_io_data), riff_ogg_io_read, NULL); + return new_sf; + } +} + +#endif /* _RIFF_OGG_STREAMFILE_H_ */ diff --git a/src/meta/vsv.c b/src/meta/vsv.c index a95b8a20..8a98b56b 100644 --- a/src/meta/vsv.c +++ b/src/meta/vsv.c @@ -69,7 +69,7 @@ VGMSTREAM * init_vgmstream_vsv(STREAMFILE *streamFile) { vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = interleave; - temp_streamFile = setup_vsv_streamfile(streamFile, start_offset, data_size); + temp_streamFile = setup_vsv_streamfile(streamFile); if (!temp_streamFile) goto fail; if (!vgmstream_open_stream(vgmstream, temp_streamFile, start_offset)) diff --git a/src/meta/vsv_streamfile.h b/src/meta/vsv_streamfile.h index 2f62fa0c..dd546a63 100644 --- a/src/meta/vsv_streamfile.h +++ b/src/meta/vsv_streamfile.h @@ -1,53 +1,40 @@ -#ifndef _VSV_STREAMFILE_H_ -#define _VSV_STREAMFILE_H_ -#include "../streamfile.h" - -typedef struct { - off_t null_offset; -} vsv_io_data; - -static size_t vsv_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, vsv_io_data* data) { - size_t bytes_read; - int i; - - bytes_read = streamfile->read(streamfile, dest, offset, length); - - /* VSVs do start at 0x00, but first line is also the header; must null it to avoid clicks */ - if (offset < data->null_offset) { - int max = data->null_offset - offset; - if (max > bytes_read) - max = bytes_read; - - for (i = 0; i < max; i++) { - dest[i] = 0; - } - } - /* VSV also has last 0x800 block with a PS-ADPCM flag of 0x10 (incorrect), but it's ignored by the decoder */ - - return bytes_read; -} - -static STREAMFILE* setup_vsv_streamfile(STREAMFILE *streamFile, off_t start_offset, size_t data_size) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - vsv_io_data io_data = {0}; - size_t io_data_size = sizeof(vsv_io_data); - - io_data.null_offset = 0x10; - - /* setup custom streamfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, vsv_io_read,NULL); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - return temp_streamFile; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} - -#endif /* _VSV_STREAMFILE_H_ */ +#ifndef _VSV_STREAMFILE_H_ +#define _VSV_STREAMFILE_H_ +#include "../streamfile.h" + +typedef struct { + off_t null_offset; +} vsv_io_data; + +static size_t vsv_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, vsv_io_data *data) { + int i; + size_t bytes = read_streamfile(dest, offset, length, sf); + + /* VSVs do start at 0x00, but first line is also the header; must null it to avoid clicks */ + if (offset < data->null_offset) { + int max = data->null_offset - offset; + if (max > bytes) + max = bytes; + + for (i = 0; i < max; i++) { + dest[i] = 0; + } + } + /* VSV also has last 0x800 block with a PS-ADPCM flag of 0x10 (incorrect), but it's ignored by the decoder */ + + return bytes; +} + +/* cleans VSV data */ +static STREAMFILE* setup_vsv_streamfile(STREAMFILE *sf) { + STREAMFILE *new_sf = NULL; + vsv_io_data io_data = {0}; + + io_data.null_offset = 0x10; + + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(vsv_io_data), vsv_io_read, NULL); + return new_sf; +} + +#endif /* _VSV_STREAMFILE_H_ */ From 667f9f31a746501ae007481e2b33fd350633bcec Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 2 Feb 2020 21:16:58 +0100 Subject: [PATCH 07/11] Add IO streamfile helper --- src/libvgmstream.vcproj | 16 ++- src/libvgmstream.vcxproj | 2 + src/libvgmstream.vcxproj.filters | 6 + src/meta/deblock_streamfile.c | 181 +++++++++++++++++++++++++++++++ src/meta/deblock_streamfile.h | 61 +++++++++++ 5 files changed, 262 insertions(+), 4 deletions(-) create mode 100644 src/meta/deblock_streamfile.c create mode 100644 src/meta/deblock_streamfile.h diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 9b89817d..37fe8667 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -256,6 +256,10 @@ RelativePath=".\meta\cri_utf.h" > + + @@ -572,10 +576,14 @@ RelativePath=".\meta\dc_asd.c" > - - + + + + diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index bebea4bc..627da89c 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -106,6 +106,7 @@ + @@ -279,6 +280,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 52cdb054..8c5ceafd 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -89,6 +89,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files @@ -376,6 +379,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/deblock_streamfile.c b/src/meta/deblock_streamfile.c new file mode 100644 index 00000000..eda65a78 --- /dev/null +++ b/src/meta/deblock_streamfile.c @@ -0,0 +1,181 @@ +#include "deblock_streamfile.h" + +//todo move to utils or something + +static void block_callback_default(STREAMFILE *sf, deblock_io_data *data) { + data->block_size = data->cfg.chunk_size; + data->skip_size = data->cfg.skip_size; + data->data_size = data->block_size - data->skip_size; + + //;VGM_LOG("DEBLOCK: of=%lx, bs=%lx, ss=%lx, ds=%lx\n", data->physical_offset, data->block_size, data->skip_size, data->data_size); +} + +static size_t deblock_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, deblock_io_data* data) { + size_t total_read = 0; + + + /* re-start when previous offset (can't map logical<>physical offsets) */ + if (data->logical_offset < 0 || offset < data->logical_offset) { + ;VGM_LOG("DEBLOCK: restart offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset); + data->physical_offset = data->cfg.stream_start; + data->logical_offset = 0x00; + data->block_size = 0; + data->data_size = 0; + data->skip_size = 0; + + data->step_count = data->cfg.step_start; +/* + data->read_count = data->cfg.read_count; +*/ + } + + /* read blocks */ + while (length > 0) { + + /* ignore EOF */ + if (offset < 0 || + (data->physical_offset >= data->cfg.stream_start + data->physical_size) || + (data->logical_size > 0 && offset > data->logical_size)) { + break; + } + + /* process new block */ + if (data->data_size <= 0) { + data->cfg.block_callback(sf, data); + + if (data->block_size <= 0) { + VGM_LOG("DEBLOCK: block size not set at %lx\n", data->physical_offset); + break; + } + } + +#if 1 + if (data->step_count > 0) { + data->step_count--; + data->physical_offset += data->block_size; + data->data_size = 0; + continue; + } +#else + /* handle blocks from multiple streams */ + { + if (data->step_count > 0) { + data->step_count--; + data->data_size = 0; /* step over this block */ + } + else if (data->read_count) {//must detect when blocks has been read + data->read_count--; /* read this block */ + + /* reset */ + if (data->step_count == 0 && data->read_count == 0) { + data->step_count = data->cfg.step_count; + data->read_count = data->cfg.read_count; + } + } + } +#endif + /* move to next block */ + if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->data_size; + data->data_size = 0; + + data->step_count = data->cfg.step_count; + //VGM_LOG("ignore at %lx + %lx, skips=%i, reads=%i\n", data->physical_offset, data->block_size, data->step_count, data->read_count); + continue; + } + + //VGM_LOG("accept at %lx + %lx, skips=%i, reads=%i\n", data->physical_offset, data->block_size, data->step_count, data->read_count); + + /* read block data */ + { + size_t bytes_consumed, bytes_done, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, sf); + + total_read += bytes_done; + dest += bytes_done; + offset += bytes_done; + length -= bytes_done; + + if (bytes_done != to_read || bytes_done == 0) { + break; /* error/EOF */ + } + } + } + + return total_read; +} + +static size_t deblock_io_size(STREAMFILE *streamfile, deblock_io_data* data) { + uint8_t buf[0x04]; + + if (data->logical_size) + return data->logical_size; + + if (data->cfg.logical_size) { + data->logical_size = data->cfg.logical_size; + return data->logical_size; + } + + /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ + deblock_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + //todo tests: + //if (logical_size > max_physical_offset) + // return 0; + //if (logical_size != data->stream_size) + // return 0; + + + return data->logical_size; +} + +/* generic "de-blocker" helper for streams divided in blocks that have weird interleaves, their + * decoder can't easily use blocked layout, or some other weird feature. It "filters" data so + * reader only sees clean data without blocks. Must pass setup config and a callback that sets + * sizes of a single block. */ +STREAMFILE* open_io_deblock_streamfile_f(STREAMFILE *sf, deblock_config_t *cfg) { + STREAMFILE *new_sf = NULL; + deblock_io_data io_data = {0}; + + /* prepare data */ + io_data.cfg = *cfg; /* memcpy */ + + if (io_data.cfg.block_callback == NULL) + io_data.cfg.block_callback = block_callback_default; + + if (io_data.cfg.stream_start < 0) + goto fail; + if (io_data.cfg.step_start < 0 || io_data.cfg.step_count < 0) + goto fail; + + if (io_data.cfg.step_count > 0) { + io_data.cfg.step_count--; + } +/* + if (io_data.cfg.read_count == 0) + io_data.cfg.read_count = 1; +*/ + io_data.physical_size = io_data.cfg.stream_size; + if (io_data.physical_size > get_streamfile_size(sf) + io_data.cfg.stream_start || io_data.physical_size == 0) + io_data.physical_size = get_streamfile_size(sf) - io_data.cfg.stream_start; + io_data.physical_end = io_data.cfg.stream_start + io_data.physical_size; + + io_data.logical_offset = -1; /* read reset */ + + //TODO: other validations + + /* setup subfile */ + new_sf = open_io_streamfile_f(sf, &io_data, sizeof(deblock_io_data), deblock_io_read, deblock_io_size); + return new_sf; +fail: + VGM_LOG("DEBLOCK: bad init\n"); + close_streamfile(sf); + return NULL; +} diff --git a/src/meta/deblock_streamfile.h b/src/meta/deblock_streamfile.h new file mode 100644 index 00000000..e54039d9 --- /dev/null +++ b/src/meta/deblock_streamfile.h @@ -0,0 +1,61 @@ +#ifndef _DEBLOCK_STREAMFILE_H_ +#define _DEBLOCK_STREAMFILE_H_ +#include "../streamfile.h" + +typedef struct deblock_config_t deblock_config_t; +typedef struct deblock_io_data deblock_io_data; + +struct deblock_config_t { + /* config (all optional) */ + size_t logical_size; /* pre-calculated size for performance (otherwise has to read the whole thing) */ + off_t stream_start; /* data start */ + size_t stream_size; /* data max */ + + size_t chunk_size; /* some size like a constant interleave */ + size_t frame_size; /* some other size */ + size_t skip_size; /* same */ + + int codec; /* codec or type variations */ + int channels; + int big_endian; + uint32_t config; /* some non-standard config value */ + + /* read=blocks from out stream to read) and "steps" (blocks from other streams to skip) */ + int step_start; /* initial step_count at stream start (often 0) */ + int step_count; /* number of blocks to step over from other streams */ + //int read_count; /* number of blocks to read from this stream, after steps */ + + size_t track_size; + int track_number; + int track_count; + uint32_t track_type; + + size_t interleave_count; + size_t interleave_last_count; + + /* callback that setups deblock_io_data state, normally block_size and data_size */ + void (*block_callback)(STREAMFILE *sf, deblock_io_data *data); +} ; + +struct deblock_io_data { + /* initial config */ + deblock_config_t cfg; + + /* state */ + off_t logical_offset; /* fake deblocked offset */ + off_t physical_offset; /* actual file offset */ + off_t block_size; /* current block (added to physical offset) */ + off_t skip_size; /* data to skip from block start to reach data (like a header) */ + off_t data_size; /* usable data in a block (added to logical offset) */ + + int step_count; /* number of blocks to step over */ + //int read_count; /* number of blocks to read */ + + size_t logical_size; + size_t physical_size; + off_t physical_end; +}; + +STREAMFILE* open_io_deblock_streamfile_f(STREAMFILE *sf, deblock_config_t *cfg); + +#endif /* _DEBLOCK_STREAMFILE_H_ */ From 6803c939e4eb32645a88d0f60c23575dbdcf6d52 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 2 Feb 2020 21:20:51 +0100 Subject: [PATCH 08/11] Clean some IO streamfile code --- src/meta/aix_streamfile.h | 184 +++++++---------------- src/meta/ea_eaac_opus_streamfile.h | 226 +--------------------------- src/meta/ea_schl_streamfile.h | 227 ++++++----------------------- src/meta/kma9_streamfile.h | 169 +++------------------ src/meta/mzrt_streamfile.h | 151 ++++--------------- src/meta/sfh_streamfile.h | 157 +++----------------- src/meta/xavs_streamfile.h | 221 ++++++++-------------------- 7 files changed, 233 insertions(+), 1102 deletions(-) diff --git a/src/meta/aix_streamfile.h b/src/meta/aix_streamfile.h index cf560f18..7001235c 100644 --- a/src/meta/aix_streamfile.h +++ b/src/meta/aix_streamfile.h @@ -1,133 +1,51 @@ -#ifndef _AIX_STREAMFILE_H_ -#define _AIX_STREAMFILE_H_ -#include "../streamfile.h" - - -typedef struct { - /* config */ - off_t stream_offset; - size_t stream_size; - int layer_number; - - /* state */ - off_t logical_offset; /* fake offset */ - off_t physical_offset; /* actual offset */ - size_t block_size; /* current size */ - size_t skip_size; /* size from block start to reach data */ - size_t data_size; /* usable size in a block */ - - size_t logical_size; -} aix_io_data; - - -static size_t aix_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, aix_io_data* data) { - size_t total_read = 0; - - - /* re-start when previous offset (can't map logical<>physical offsets) */ - if (data->logical_offset < 0 || offset < data->logical_offset) { - data->physical_offset = data->stream_offset; - data->logical_offset = 0x00; - data->data_size = 0; - } - - /* read blocks */ - while (length > 0) { - - /* ignore EOF */ - if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { - break; - } - - /* process new block */ - if (data->data_size == 0) { - uint32_t block_id = read_u32be(data->physical_offset+0x00, streamfile); - data->block_size = read_u32be(data->physical_offset+0x04, streamfile) + 0x08; - - /* check valid block "AIXP" id, knowing that AIX segments end with "AIXE" block too */ - if (block_id != 0x41495850 || data->block_size == 0 || data->block_size == 0xFFFFFFFF) { - break; - } - - /* read target layer, otherwise skip to next block and try again */ - if (read_s8(data->physical_offset+0x08, streamfile) == data->layer_number) { - /* 0x09(1): layer count */ - data->data_size = read_s16be(data->physical_offset+0x0a, streamfile); - /* 0x0c: -1 */ - data->skip_size = 0x10; - } - - /* strange AIX in Tetris Collection (PS2) with padding before ADX start (no known flag) */ - if (data->logical_offset == 0x00 && - read_u32be(data->physical_offset + 0x10, streamfile) == 0 && - read_u16be(data->physical_offset + data->block_size - 0x28, streamfile) == 0x8000) { - data->data_size = 0x28; - data->skip_size = data->block_size - 0x28; - } - } - - /* move to next block */ - if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { - data->physical_offset += data->block_size; - data->logical_offset += data->data_size; - data->data_size = 0; - continue; - } - - /* read data */ - { - size_t bytes_consumed, bytes_done, to_read; - - bytes_consumed = offset - data->logical_offset; - to_read = data->data_size - bytes_consumed; - if (to_read > length) - to_read = length; - bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); - - total_read += bytes_done; - dest += bytes_done; - offset += bytes_done; - length -= bytes_done; - - if (bytes_done != to_read || bytes_done == 0) { - break; /* error/EOF */ - } - } - } - - return total_read; -} - -static size_t aix_io_size(STREAMFILE *streamfile, aix_io_data* data) { - uint8_t buf[1]; - - if (data->logical_size) - return data->logical_size; - - /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ - aix_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); - data->logical_size = data->logical_offset; - - return data->logical_size; -} - -/* Handles deinterleaving of AIX blocked layer streams */ -static STREAMFILE* setup_aix_streamfile(STREAMFILE *sf, off_t stream_offset, size_t stream_size, int layer_number, const char* extension) { - STREAMFILE *new_sf = NULL; - aix_io_data io_data = {0}; - - io_data.stream_offset = stream_offset; - io_data.stream_size = stream_size; - io_data.layer_number = layer_number; - io_data.logical_offset = -1; /* force reset */ - - new_sf = open_wrap_streamfile(sf); - new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(aix_io_data), aix_io_read, aix_io_size); - new_sf = open_buffer_streamfile_f(new_sf, 0); - if (extension) { - new_sf = open_fakename_streamfile_f(new_sf, NULL, extension); - } - return new_sf; -} - -#endif /* _AIX_STREAMFILE_H_ */ +#ifndef _AIX_STREAMFILE_H_ +#define _AIX_STREAMFILE_H_ +#include "deblock_streamfile.h" + +//todo block size must check >= stream_size + +static void block_callback(STREAMFILE *sf, deblock_io_data *data) { + uint32_t block_id = read_u32be(data->physical_offset + 0x00, sf); + data->block_size = read_u32be(data->physical_offset + 0x04, sf) + 0x08; + + /* check "AIXP" id, (AIX segments end with "AIXE" too) */ + if (block_id != 0x41495850) { + return; + } + + /* read target layer, otherwise ignore block */ + if (read_s8(data->physical_offset + 0x08, sf) == data->cfg.track_number) { + /* 0x09(1): layer count */ + data->data_size = read_s16be(data->physical_offset + 0x0a, sf); + /* 0x0c: -1 */ + data->skip_size = 0x10; + } + + /* strange AIX in Tetris Collection (PS2) with padding before ADX start (no known flag) */ + if (data->logical_offset == 0x00 && + read_u32be(data->physical_offset + 0x10, sf) == 0 && + read_u16be(data->physical_offset + data->block_size - 0x28, sf) == 0x8000) { + data->data_size = 0x28; + data->skip_size = data->block_size - 0x28; + } +} + +/* Deinterleaves AIX layers */ +static STREAMFILE* setup_aix_streamfile(STREAMFILE *sf, off_t stream_offset, size_t stream_size, int layer_number, const char* extension) { + STREAMFILE *new_sf = NULL; + deblock_config_t cfg = {0}; + + cfg.stream_start = stream_offset; + cfg.stream_size = stream_size; + cfg.track_number = layer_number; + cfg.block_callback = block_callback; + + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_deblock_streamfile_f(new_sf, &cfg); + //new_sf = open_buffer_streamfile_f(new_sf, 0); + if (extension) + new_sf = open_fakename_streamfile_f(new_sf, NULL, extension); + return new_sf; +} + +#endif /* _AIX_STREAMFILE_H_ */ diff --git a/src/meta/ea_eaac_opus_streamfile.h b/src/meta/ea_eaac_opus_streamfile.h index a71b810e..06a40e89 100644 --- a/src/meta/ea_eaac_opus_streamfile.h +++ b/src/meta/ea_eaac_opus_streamfile.h @@ -1,241 +1,23 @@ #ifndef _EA_EAAC_OPUS_STREAMFILE_H_ #define _EA_EAAC_OPUS_STREAMFILE_H_ -#include "../streamfile.h" +#include "deblock_streamfile.h" -typedef struct deblock_config_t deblock_config_t; -typedef struct deblock_io_data deblock_io_data; - - struct deblock_config_t { - /* config (all optional) */ - size_t logical_size; /* pre-calculated size for performance (otherwise has to read the whole thing) */ - off_t stream_start; /* data start */ - size_t stream_size; /* data max */ - - size_t chunk_size; /* some size like a constant interleave */ - size_t skip_size; /* same */ - - int codec; /* codec or type variations */ - int channels; - int big_endian; - uint32_t config; /* some non-standard config value */ - - /* read=blocks from out stream to read) and "steps" (blocks from other streams to skip) */ - int step_start; /* initial step_count at stream start (often 0) */ - int step_count; /* number of blocks to step over from other streams */ - int read_count; /* number of blocks to read from this stream, after steps */ - - size_t track_size; - int track_number; - int track_count; - size_t interleave_count; - size_t interleave_last_count; - - /* callback that setups deblock_io_data state, normally block_size and data_size */ - void (*block_callback)(STREAMFILE *sf, off_t offset, deblock_io_data *data); -} ; - - -struct deblock_io_data{ - /* initial config */ - deblock_config_t cfg; - - /* state */ - off_t logical_offset; /* fake deblocked offset */ - off_t physical_offset; /* actual file offset */ - off_t block_size; /* current block (added to physical offset) */ - off_t skip_size; /* data to skip from block start to reach data (like a header) */ - off_t data_size; /* usable data in a block (added to logical offset) */ -//todo head/foot? - int step_count; /* number of blocks to step over */ - int read_count; /* number of blocks to read */ - - size_t logical_size; - size_t physical_size; - off_t physical_end; -} ; - - -static void block_callback_default(STREAMFILE *sf, off_t offset, deblock_io_data *data) { - data->block_size = data->cfg.chunk_size; - data->skip_size = data->cfg.skip_size; - data->data_size = data->block_size - data->skip_size; -} - -static size_t deblock_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, deblock_io_data* data) { - size_t total_read = 0; - - - /* re-start when previous offset (can't map logical<>physical offsets) */ - if (data->logical_offset < 0 || offset < data->logical_offset) { - ;VGM_LOG("DEBLOCK: restart offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset); - data->physical_offset = data->cfg.stream_start; - data->logical_offset = 0x00; - data->block_size = 0; - data->data_size = 0; - data->skip_size = 0; - - data->step_count = data->cfg.step_start; - data->read_count = data->cfg.read_count; - } - - /* read blocks */ - while (length > 0) { - - /* ignore EOF */ - if (offset < 0 || - (data->physical_offset >= data->cfg.stream_start + data->physical_size) || - (data->logical_size > 0 && offset > data->logical_size)) { - break; - } - - /* process new block */ - if (data->data_size <= 0) { - data->cfg.block_callback(sf, offset, data); - - if (data->block_size <= 0) { - VGM_LOG("DEBLOCK: block size not set at %lx\n", data->physical_offset); - break; - } - } - -#if 1 - if (data->step_count > 0) { - data->step_count--; - data->physical_offset += data->block_size; - data->data_size = 0; - continue; - } -#else - /* handle blocks from multiple streams */ - { - if (data->step_count > 0) { - data->step_count--; - data->data_size = 0; /* step over this block */ - } - else if (data->read_count) {//must detect when blocks has been read - data->read_count--; /* read this block */ - - /* reset */ - if (data->step_count == 0 && data->read_count == 0) { - data->step_count = data->cfg.step_count; - data->read_count = data->cfg.read_count; - } - } - } -#endif - /* move to next block */ - if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { - data->physical_offset += data->block_size; - data->logical_offset += data->data_size; - data->data_size = 0; - - data->step_count = data->cfg.step_count; - //VGM_LOG("ignore at %lx + %lx, skips=%i, reads=%i\n", data->physical_offset, data->block_size, data->step_count, data->read_count); - continue; - } - - //VGM_LOG("accept at %lx + %lx, skips=%i, reads=%i\n", data->physical_offset, data->block_size, data->step_count, data->read_count); - - /* read block data */ - { - size_t bytes_consumed, bytes_done, to_read; - - bytes_consumed = offset - data->logical_offset; - to_read = data->data_size - bytes_consumed; - if (to_read > length) - to_read = length; - bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, sf); - - total_read += bytes_done; - dest += bytes_done; - offset += bytes_done; - length -= bytes_done; - - if (bytes_done != to_read || bytes_done == 0) { - break; /* error/EOF */ - } - } - } - - return total_read; -} - -static size_t deblock_io_size(STREAMFILE *streamfile, deblock_io_data* data) { - uint8_t buf[0x04]; - - if (data->logical_size) - return data->logical_size; - - if (data->cfg.logical_size) { - data->logical_size = data->cfg.logical_size; - return data->logical_size; - } - - /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ - deblock_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); - data->logical_size = data->logical_offset; - return data->logical_size; -} - -/* generic "de-blocker" helper for streams divided in blocks that have weird interleaves, their - * decoder can't easily use blocked layout, or some other weird feature. Must pass a - * deblock_config_t with setup and a callback that sets sizes of a single block. */ -static STREAMFILE* open_io_deblocker_streamfile_f(STREAMFILE *sf, deblock_config_t *cfg) { - STREAMFILE *new_sf = NULL; - deblock_io_data io_data = {0}; - - /* prepare data */ - io_data.cfg = *cfg; /* memcpy */ - - if (io_data.cfg.block_callback == NULL) - io_data.cfg.block_callback = block_callback_default; - - if (io_data.cfg.stream_start < 0) - goto fail; - if (io_data.cfg.step_start < 0 || io_data.cfg.step_count < 0) - goto fail; - - if (io_data.cfg.read_count == 0) - io_data.cfg.read_count = 1; - - io_data.physical_size = io_data.cfg.stream_size; - if (io_data.physical_size > get_streamfile_size(sf) + io_data.cfg.stream_start || io_data.physical_size == 0) - io_data.physical_size = get_streamfile_size(sf) - io_data.cfg.stream_start; - io_data.physical_end = io_data.cfg.stream_start + io_data.physical_size; -VGM_LOG("ps=%x, pe=%lx\n", io_data.physical_size, io_data.physical_end); - io_data.logical_offset = -1; /* read reset */ - - //TODO: other validations - - /* setup subfile */ - new_sf = open_io_streamfile_f(sf, &io_data, sizeof(deblock_io_data), deblock_io_read, deblock_io_size); - return new_sf; -fail: - VGM_LOG("DEBLOCK: bad init\n"); - close_streamfile(sf); - return NULL; -} - -/*****************************************************/ - -static void block_callback(STREAMFILE *sf, off_t offset, deblock_io_data *data) { - /* read the whole block, will be skipped for unwanted sub-streams */ +static void block_callback(STREAMFILE *sf, deblock_io_data *data) { data->block_size = 0x02 + read_u16be(data->physical_offset, sf); data->data_size = data->block_size; - //VGM_LOG("read at %lx + %lx, skips=%i, reads=%i\n", data->physical_offset, data->block_size, data->step_count, data->read_count); } static STREAMFILE* open_io_eaac_opus_streamfile_f(STREAMFILE *new_sf, int stream_number, int stream_count) { deblock_config_t cfg = {0}; cfg.step_start = stream_number; - cfg.step_count = stream_count - 1; + cfg.step_count = stream_count; cfg.block_callback = block_callback; /* starts from 0 since new_sf is pre-deblocked */ /* setup subfile */ //new_sf = open_wrap_streamfile(sf); /* to be used with others */ - new_sf = open_io_deblocker_streamfile_f(new_sf, &cfg); + new_sf = open_io_deblock_streamfile_f(new_sf, &cfg); return new_sf; } diff --git a/src/meta/ea_schl_streamfile.h b/src/meta/ea_schl_streamfile.h index 97eb0c12..e2711b77 100644 --- a/src/meta/ea_schl_streamfile.h +++ b/src/meta/ea_schl_streamfile.h @@ -1,182 +1,45 @@ -#ifndef _EA_SCHL_STREAMFILE_H_ -#define _EA_SCHL_STREAMFILE_H_ -#include "../streamfile.h" - - -typedef struct { - /* state */ - off_t logical_offset; /* offset that corresponds to physical_offset */ - off_t physical_offset; /* actual file offset */ - - /* config */ - int codec; - int channels; - off_t start_offset; - size_t total_size; /* size of the resulting substream */ -} schl_io_data; - - -/* Reads skipping EA's block headers, so the resulting data is smaller or larger than physical data. - * physical/logical_offset should always be at the start of a block and only advance when a block is fully done */ -static size_t schl_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, schl_io_data* data) { - size_t total_read = 0; - - /* ignore bad reads */ - if (offset < 0 || offset > data->total_size) { - return total_read; - } - - /* previous offset: re-start as we can't map logical<>physical offsets - * (kinda slow as it trashes buffers, but shouldn't happen often) */ - if (offset < data->logical_offset) { - data->physical_offset = data->start_offset; - data->logical_offset = 0x00; - } - - /* read doing one EA block at a time */ - while (length > 0) { - size_t to_read, bytes_read; - off_t intrablock_offset, intradata_offset; - uint32_t block_id, block_size, data_size, skip_size; - - block_id = (uint32_t)read_32bitBE(data->physical_offset+0x00,streamfile); - block_size = read_32bitLE(data->physical_offset+0x04,streamfile); /* always LE, hopefully */ - - if (block_id == 0x5343456C) /* "SCEl" */ - break; /* end block (no need to look for more SCHl for codecs needed this custom IO) */ - - if (block_id != 0x5343446C) { /* "SCDl" */ - data->physical_offset += block_size; - continue; /* skip non-data blocks */ - } - - switch(data->codec) { - case 0x1b: /* ATRAC3plus */ - data_size = read_32bitLE(data->physical_offset+0x0c+0x04*data->channels,streamfile); - skip_size = 0x0c+0x04*data->channels+0x04; - break; - default: - return total_read; - } - - /* requested offset is outside current block, try next */ - if (offset >= data->logical_offset + data_size) { - data->physical_offset += block_size; - data->logical_offset += data_size; - continue; - } - - /* reads could fall in the middle of the block */ - intradata_offset = offset - data->logical_offset; - intrablock_offset = skip_size + intradata_offset; - - /* clamp reads up to this block's end */ - to_read = (data_size - intradata_offset); - if (to_read > length) - to_read = length; - if (to_read == 0) - break; /* should never happen... */ - - /* finally read and move buffer/offsets */ - bytes_read = read_streamfile(dest, data->physical_offset + intrablock_offset, to_read, streamfile); - total_read += bytes_read; - if (bytes_read != to_read) - break; /* couldn't read fully */ - - dest += bytes_read; - offset += bytes_read; - length -= bytes_read; - - /* block fully read, go next */ - if (intradata_offset + bytes_read == data_size) { - data->physical_offset += block_size; - data->logical_offset += data_size; - } - } - - return total_read; -} - -static size_t schl_io_size(STREAMFILE *streamfile, schl_io_data* data) { - off_t physical_offset, max_physical_offset; - size_t total_size = 0; - - if (data->total_size) - return data->total_size; - - physical_offset = data->start_offset; - max_physical_offset = get_streamfile_size(streamfile); - - /* get size of the underlying, non-blocked data */ - while (physical_offset < max_physical_offset) { - uint32_t block_id, block_size, data_size; - - block_id = (uint32_t)read_32bitBE(physical_offset+0x00,streamfile); - block_size = read_32bitLE(physical_offset+0x04,streamfile); /* always LE, hopefully */ - - if (block_id == 0x5343456C) /* "SCEl" */ - break; /* end block (no need to look for more SCHl for codecs needed this custom IO) */ - - if (block_id != 0x5343446C) { /* "SCDl" */ - physical_offset += block_size; - continue; /* skip non-data blocks */ - } - - switch(data->codec) { - case 0x1b: /* ATRAC3plus */ - data_size = read_32bitLE(physical_offset+0x0c+0x04*data->channels,streamfile); - break; - default: - return 0; - } - - physical_offset += block_size; - total_size += data_size; - } - - - if (total_size > get_streamfile_size(streamfile)) { - VGM_LOG("EA SCHL: wrong streamfile total_size\n"); - total_size = 0; - } - - data->total_size = total_size; - return data->total_size; -} - - -/* Prepares custom IO for some blocked SCHl formats, that need clean reads without block headers. - * Basically done to feed FFmpeg clean ATRAC3plus. - */ -static STREAMFILE* setup_schl_streamfile(STREAMFILE *streamFile, int codec, int channels, off_t start_offset, size_t total_size) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - schl_io_data io_data = {0}; - size_t io_data_size = sizeof(schl_io_data); - - io_data.codec = codec; - io_data.channels = channels; - io_data.start_offset = start_offset; - io_data.total_size = total_size; /* optional */ - io_data.physical_offset = start_offset; - - /* setup subfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, schl_io_read,schl_io_size); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_buffer_streamfile(new_streamFile,0); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - return temp_streamFile; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} - -#endif /* _EA_SCHL_STREAMFILE_H_ */ +#ifndef _EA_SCHL_STREAMFILE_H_ +#define _EA_SCHL_STREAMFILE_H_ +#include "deblock_streamfile.h" + +static void block_callback(STREAMFILE *sf, deblock_io_data *data) { + uint32_t block_type, block_size; + + block_type = read_u32be(data->physical_offset + 0x00, sf); + block_size = read_u32le(data->physical_offset + 0x04, sf); /* always LE, hopefully */ + + if (block_type == 0x5343456C) /* "SCEl" end block */ + return; + + data->block_size = block_size; + if (block_type != 0x5343446C) /* skip non-"SCDl" blocks */ + return; + + switch(data->cfg.codec) { + case 0x1b: /* ATRAC3plus */ + data->data_size = read_32bitLE(data->physical_offset + 0x0c + 0x04 * data->cfg.channels, sf); + data->skip_size = 0x0c + 0x04 * data->cfg.channels + 0x04; + break; + default: + break; + } +} + +/* Deblocks SCHl streams */ +static STREAMFILE* setup_schl_streamfile(STREAMFILE *sf, int codec, int channels, off_t stream_offset, size_t logical_size) { + STREAMFILE *new_sf = NULL; + deblock_config_t cfg = {0}; + + cfg.stream_start = stream_offset; + cfg.logical_size = logical_size; + cfg.codec = codec; + cfg.channels = channels; //todo chunk size? + cfg.block_callback = block_callback; + + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_deblock_streamfile_f(new_sf, &cfg); + //new_sf = open_buffer_streamfile(new_sf, 0); + return new_sf; +} + +#endif /* _EA_SCHL_STREAMFILE_H_ */ diff --git a/src/meta/kma9_streamfile.h b/src/meta/kma9_streamfile.h index 34f21dde..691c9cbd 100644 --- a/src/meta/kma9_streamfile.h +++ b/src/meta/kma9_streamfile.h @@ -1,148 +1,21 @@ -#ifndef _KM9_STREAMFILE_H_ -#define _KM9_STREAMFILE_H_ -#include "../streamfile.h" - - -typedef struct { - /* config */ - int stream_number; - int stream_count; - size_t interleave_size; - off_t stream_offset; - size_t stream_size; - - /* state */ - off_t logical_offset; /* offset that corresponds to physical_offset */ - off_t physical_offset; /* actual file offset */ - - size_t skip_size; /* size to skip from a block start to reach data start */ - size_t data_size; /* logical size of the block */ - - size_t logical_size; -} kma9_io_data; - - -static size_t kma9_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, kma9_io_data* data) { - size_t total_read = 0; - - /* ignore bad reads */ - if (offset < 0 || offset > data->logical_size) { - return 0; - } - - /* previous offset: re-start as we can't map logical<>physical offsets - * (kinda slow as it trashes buffers, but shouldn't happen often) */ - if (offset < data->logical_offset) { - data->logical_offset = 0x00; - data->physical_offset = data->stream_offset; - data->data_size = 0; - } - - /* read blocks, one at a time */ - while (length > 0) { - - /* ignore EOF */ - if (data->logical_offset >= data->logical_size) { - break; - } - - /* process new block */ - if (data->data_size == 0) { - data->skip_size = data->interleave_size * data->stream_number; - data->data_size = data->interleave_size; - } - - /* move to next block */ - if (offset >= data->logical_offset + data->data_size) { - data->physical_offset += data->interleave_size*data->stream_count; - data->logical_offset += data->data_size; - data->data_size = 0; - continue; - } - - /* read data */ - { - size_t bytes_consumed, bytes_done, to_read; - - bytes_consumed = offset - data->logical_offset; - - to_read = data->data_size - bytes_consumed; - if (to_read > length) - to_read = length; - bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); - - offset += bytes_done; - total_read += bytes_done; - length -= bytes_done; - dest += bytes_done; - - if (bytes_done != to_read || bytes_done == 0) { - break; /* error/EOF */ - } - } - - } - - return total_read; -} - -static size_t kma9_io_size(STREAMFILE *streamfile, kma9_io_data* data) { - off_t physical_offset = data->stream_offset; - off_t max_physical_offset = get_streamfile_size(streamfile); - size_t logical_size = 0; - - if (data->logical_size) - return data->logical_size; - - /* get size of the logical stream */ - while (physical_offset < max_physical_offset) { - logical_size += data->interleave_size; - physical_offset += data->interleave_size*data->stream_count; - } - - if (logical_size > max_physical_offset) - return 0; - if (logical_size != data->stream_size) - return 0; - data->logical_size = logical_size; - return data->logical_size; -} - - -/* Prepares custom IO for KMA9, which interleaves ATRAC9 frames */ -static STREAMFILE* setup_kma9_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, size_t interleave_size, int stream_number, int stream_count) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - kma9_io_data io_data = {0}; - size_t io_data_size = sizeof(kma9_io_data); - - io_data.stream_number = stream_number; - io_data.stream_count = stream_count; - io_data.stream_offset = stream_offset; - io_data.stream_size = stream_size; - io_data.interleave_size = interleave_size; - io_data.physical_offset = stream_offset; - io_data.logical_size = kma9_io_size(streamFile, &io_data); /* force init */ - - if (io_data.logical_size == 0) { - VGM_LOG("KMA9: wrong logical size\n"); - goto fail; - } - - /* setup subfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, kma9_io_read,kma9_io_size); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - return temp_streamFile; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} - - -#endif /* _KM9_STREAMFILE_H_ */ +#ifndef _KM9_STREAMFILE_H_ +#define _KM9_STREAMFILE_H_ +#include "deblock_streamfile.h" + +/* Deinterleaves KMA9 streams */ +static STREAMFILE* setup_kma9_streamfile(STREAMFILE *sf, off_t stream_offset, size_t stream_size, size_t interleave_size, int stream_number, int stream_count) { + STREAMFILE *new_sf = NULL; + deblock_config_t cfg = {0}; + + cfg.stream_start = stream_offset; + cfg.logical_size = stream_size; + cfg.chunk_size = interleave_size; + cfg.step_start = stream_number; + cfg.step_count = stream_count; + + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_deblock_streamfile_f(new_sf, &cfg); + return new_sf; +} + +#endif /* _KM9_STREAMFILE_H_ */ diff --git a/src/meta/mzrt_streamfile.h b/src/meta/mzrt_streamfile.h index f9483d37..e187c68e 100644 --- a/src/meta/mzrt_streamfile.h +++ b/src/meta/mzrt_streamfile.h @@ -1,124 +1,27 @@ -#ifndef _MZRT_STREAMFILE_H_ -#define _MZRT_STREAMFILE_H_ -#include "../streamfile.h" - - -typedef struct { - /* config */ - off_t stream_offset; - size_t stream_size; - - /* state */ - off_t logical_offset; /* fake offset */ - off_t physical_offset; /* actual offset */ - size_t block_size; /* current size */ - size_t skip_size; /* size from block start to reach data */ - size_t data_size; /* usable size in a block */ - - size_t logical_size; -} mzrt_io_data; - - -static size_t mzrt_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, mzrt_io_data* data) { - size_t total_read = 0; - - - /* re-start when previous offset (can't map logical<>physical offsets) */ - if (data->logical_offset < 0 || offset < data->logical_offset) { - data->physical_offset = data->stream_offset; - data->logical_offset = 0x00; - data->data_size = 0; - } - - /* read blocks */ - while (length > 0) { - - /* ignore EOF */ - if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { - break; - } - - /* process new block */ - if (data->data_size == 0) { - /* 0x00: samples in this block */ - data->data_size = read_32bitBE(data->stream_offset + 0x04, streamfile); - data->skip_size = 0x08; - data->block_size = data->skip_size + data->data_size; - } - - /* move to next block */ - if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { - data->physical_offset += data->block_size; - data->logical_offset += data->data_size; - data->data_size = 0; - continue; - } - - /* read data */ - { - size_t bytes_consumed, bytes_done, to_read; - - bytes_consumed = offset - data->logical_offset; - to_read = data->data_size - bytes_consumed; - if (to_read > length) - to_read = length; - bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); - - total_read += bytes_done; - dest += bytes_done; - offset += bytes_done; - length -= bytes_done; - - if (bytes_done != to_read || bytes_done == 0) { - break; /* error/EOF */ - } - } - } - - return total_read; -} - -static size_t mzrt_io_size(STREAMFILE *streamfile, mzrt_io_data* data) { - uint8_t buf[1]; - - if (data->logical_size) - return data->logical_size; - - /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ - mzrt_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); - data->logical_size = data->logical_offset; - - return data->logical_size; -} - -/* Handles deinterleaving of MZRT blocked streams */ -static STREAMFILE* setup_mzrt_streamfile(STREAMFILE *streamFile, off_t stream_offset) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - mzrt_io_data io_data = {0}; - size_t io_data_size = sizeof(mzrt_io_data); - - io_data.stream_offset = stream_offset; - io_data.stream_size = get_streamfile_size(streamFile) - stream_offset; - io_data.logical_offset = -1; /* force phys offset reset */ - - /* setup subfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, mzrt_io_read,mzrt_io_size); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_buffer_streamfile(new_streamFile,0); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - return temp_streamFile; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} - -#endif /* _MZRT_STREAMFILE_H_ */ +#ifndef _MZRT_STREAMFILE_H_ +#define _MZRT_STREAMFILE_H_ +#include "deblock_streamfile.h" + +static void block_callback(STREAMFILE *sf, deblock_io_data *data) { + /* 0x00: samples in this block */ + data->data_size = read_s32be(data->physical_offset + 0x04, sf); + data->skip_size = 0x08; + data->block_size = data->skip_size + data->data_size; +} + +/* Deblocks MZRT streams */ +static STREAMFILE* setup_mzrt_streamfile(STREAMFILE *sf, off_t stream_offset) { + STREAMFILE *new_sf = NULL; + deblock_config_t cfg = {0}; + + cfg.stream_start = stream_offset; + cfg.block_callback = block_callback; + + /* setup sf */ + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_deblock_streamfile_f(new_sf, &cfg); + //new_sf = open_buffer_streamfile_f(new_sf, 0); + return new_sf; +} + +#endif /* _MZRT_STREAMFILE_H_ */ diff --git a/src/meta/sfh_streamfile.h b/src/meta/sfh_streamfile.h index 954df1a0..9af0990a 100644 --- a/src/meta/sfh_streamfile.h +++ b/src/meta/sfh_streamfile.h @@ -1,133 +1,24 @@ -#ifndef _SFH_STREAMFILE_H_ -#define _SFH_STREAMFILE_H_ -#include "../streamfile.h" - - -typedef struct { - /* config */ - off_t stream_offset; - size_t stream_size; - - /* state */ - off_t logical_offset; /* fake offset */ - off_t physical_offset; /* actual offset */ - size_t block_size; /* current size */ - size_t skip_size; /* size from block start to reach data */ - size_t data_size; /* usable size in a block */ - - size_t logical_size; -} sfh_io_data; - - -static size_t sfh_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, sfh_io_data* data) { - size_t total_read = 0; - - - /* re-start when previous offset (can't map logical<>physical offsets) */ - if (data->logical_offset < 0 || offset < data->logical_offset) { - data->physical_offset = data->stream_offset; - data->logical_offset = 0x00; - data->data_size = 0; - } - - /* read blocks */ - while (length > 0) { - - /* ignore EOF */ - if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { - break; - } - - /* process new block */ - if (data->data_size == 0) { - data->skip_size = 0x10; /* skip 0x10 garbage on every block */ - data->data_size = data->block_size - 0x10; - } - - /* move to next block */ - if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { - data->physical_offset += data->block_size; - data->logical_offset += data->data_size; - data->data_size = 0; - continue; - } - - /* read data */ - { - size_t bytes_consumed, bytes_done, to_read; - - bytes_consumed = offset - data->logical_offset; - to_read = data->data_size - bytes_consumed; - if (to_read > length) - to_read = length; - bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); - - total_read += bytes_done; - dest += bytes_done; - offset += bytes_done; - length -= bytes_done; - - if (bytes_done != to_read || bytes_done == 0) { - break; /* error/EOF */ - } - } - } - - return total_read; -} - -static size_t sfh_io_size(STREAMFILE *streamfile, sfh_io_data* data) { - uint8_t buf[1]; - - if (data->logical_size) - return data->logical_size; - - /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ - sfh_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); - data->logical_size = data->logical_offset; - - return data->logical_size; -} - -/* Handles deinterleaving of SFH blocked streams */ -static STREAMFILE* setup_sfh_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t block_size, size_t clean_size, const char* extension) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - sfh_io_data io_data = {0}; - size_t io_data_size = sizeof(sfh_io_data); - - io_data.stream_offset = stream_offset; - io_data.stream_size = get_streamfile_size(streamFile) - stream_offset; - io_data.block_size = block_size; - io_data.logical_offset = -1; /* force phys offset reset */ - - /* setup subfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, sfh_io_read,sfh_io_size); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_buffer_streamfile(new_streamFile,0); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_clamp_streamfile(new_streamFile,0x00, clean_size); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - if (extension) { - new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,extension); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - } - - return temp_streamFile; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} - -#endif /* _SFH_STREAMFILE_H_ */ +#ifndef _SFH_STREAMFILE_H_ +#define _SFH_STREAMFILE_H_ +#include "deblock_streamfile.h" + +/* Deblocks SFH streams, skipping 0x10 garbage added to every chunk */ +static STREAMFILE* setup_sfh_streamfile(STREAMFILE *sf, off_t stream_offset, size_t chunk_size, size_t clean_size, const char* extension) { + STREAMFILE *new_sf = NULL; + deblock_config_t cfg = {0}; + + cfg.stream_start = stream_offset; + cfg.chunk_size = chunk_size; + cfg.skip_size = 0x10; + + /* setup sf */ + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_deblock_streamfile_f(new_sf, &cfg); + //new_sf = open_buffer_streamfile_f(new_sf, 0); + new_sf = open_clamp_streamfile_f(new_sf, 0x00, clean_size); + if (extension) + new_sf = open_fakename_streamfile_f(new_sf, NULL, extension); + return new_sf; +} + +#endif /* _SFH_STREAMFILE_H_ */ diff --git a/src/meta/xavs_streamfile.h b/src/meta/xavs_streamfile.h index ac5e7d3b..62aa1056 100644 --- a/src/meta/xavs_streamfile.h +++ b/src/meta/xavs_streamfile.h @@ -1,160 +1,61 @@ -#ifndef _XAVS_STREAMFILE_H_ -#define _XAVS_STREAMFILE_H_ -#include "../streamfile.h" - - -typedef struct { - /* config */ - off_t stream_offset; - size_t stream_size; - int stream_number; - - /* state */ - off_t logical_offset; /* fake offset */ - off_t physical_offset; /* actual offset */ - size_t block_size; /* current size */ - size_t skip_size; /* size from block start to reach data */ - size_t data_size; /* usable size in a block */ - - size_t logical_size; -} xavs_io_data; - - -static size_t xavs_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, xavs_io_data* data) { - size_t total_read = 0; - - - /* re-start when previous offset (can't map logical<>physical offsets) */ - if (data->logical_offset < 0 || offset < data->logical_offset) { - data->physical_offset = data->stream_offset; - data->logical_offset = 0x00; - data->data_size = 0; - } - - /* read blocks */ - while (length > 0) { - - /* ignore EOF */ - if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { - break; - } - - /* process new block */ - if (data->data_size == 0) { - uint32_t chunk_id = read_32bitLE(data->physical_offset+0x00, streamfile) & 0xFF; - uint32_t chunk_size = read_32bitLE(data->physical_offset+0x00, streamfile) >> 8; - - data->skip_size = 0x04; - - switch(chunk_id) { - /* audio */ - case 0x41: - case 0x61: - case 0x62: - case 0x63: - data->block_size = 0x04 + chunk_size; - if (data->stream_number + 1 == (chunk_id & 0x0F)) { - data->data_size = chunk_size; - } else { - data->data_size = 0; /* ignore other subsongs */ - } - break; - - /* video */ - case 0x56: - data->block_size = 0x04 + chunk_size; - data->data_size = 0; - break; - - /* empty */ - case 0x21: /* related to video */ - case 0x5F: /* "_EOS" */ - data->block_size = 0x04; - data->data_size = 0; - break; - - default: - VGM_LOG("XAVS: unknown type at %lx\n", data->physical_offset); - data->block_size = 0x04; - data->data_size = 0; - break; - } - } - - /* move to next block */ - if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { - data->physical_offset += data->block_size; - data->logical_offset += data->data_size; - data->data_size = 0; - continue; - } - - /* read data */ - { - size_t bytes_consumed, bytes_done, to_read; - - bytes_consumed = offset - data->logical_offset; - to_read = data->data_size - bytes_consumed; - if (to_read > length) - to_read = length; - bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); - - total_read += bytes_done; - dest += bytes_done; - offset += bytes_done; - length -= bytes_done; - - if (bytes_done != to_read || bytes_done == 0) { - break; /* error/EOF */ - } - } - } - - return total_read; -} - -static size_t xavs_io_size(STREAMFILE *streamfile, xavs_io_data* data) { - uint8_t buf[1]; - - if (data->logical_size) - return data->logical_size; - - /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ - xavs_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); - data->logical_size = data->logical_offset; - - return data->logical_size; -} - -/* Handles deinterleaving of XAVS blocked streams */ -static STREAMFILE* setup_xavs_streamfile(STREAMFILE *streamFile, off_t stream_offset, int stream_number) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - xavs_io_data io_data = {0}; - size_t io_data_size = sizeof(xavs_io_data); - - io_data.stream_offset = stream_offset; - io_data.stream_size = get_streamfile_size(streamFile) - stream_offset; - io_data.stream_number = stream_number; - io_data.logical_offset = -1; /* force phys offset reset */ - - /* setup subfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, xavs_io_read,xavs_io_size); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_buffer_streamfile(new_streamFile,0); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - return temp_streamFile; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} - -#endif /* _XAVS_STREAMFILE_H_ */ +#ifndef _XAVS_STREAMFILE_H_ +#define _XAVS_STREAMFILE_H_ +#include "deblock_streamfile.h" + +static void block_callback(STREAMFILE *sf, deblock_io_data *data) { + uint32_t chunk_type = read_u32le(data->physical_offset, sf) & 0xFF; + uint32_t chunk_size = read_u32le(data->physical_offset, sf) >> 8; + + data->skip_size = 0x04; + + switch(chunk_type) { + /* audio */ + case 0x41: + case 0x61: + case 0x62: + case 0x63: + data->block_size = 0x04 + chunk_size; + if (data->cfg.track_number + 1 == (chunk_type & 0x0F)) { + data->data_size = chunk_size; + } else { + data->data_size = 0; /* ignore other subsongs */ + } + break; + + /* video */ + case 0x56: + data->block_size = 0x04 + chunk_size; + data->data_size = 0; + break; + + /* empty */ + case 0x21: /* related to video */ + case 0x5F: /* "_EOS" */ + data->block_size = 0x04; + data->data_size = 0; + break; + + default: + VGM_LOG("XAVS: unknown type at %lx\n", data->physical_offset); + data->block_size = 0x04; + data->data_size = 0; + break; + } +} + +/* Deblocks XAVS video/audio data */ +static STREAMFILE* setup_xavs_streamfile(STREAMFILE *sf, off_t stream_offset, int stream_number) { + STREAMFILE *new_sf = NULL; + deblock_config_t cfg = {0}; + + cfg.stream_start = stream_offset; + cfg.track_number = stream_number; + cfg.block_callback = block_callback; + + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_deblock_streamfile_f(new_sf, &cfg); + //new_sf = open_buffer_streamfile(new_sf,0); + return new_sf; +} + +#endif /* _XAVS_STREAMFILE_H_ */ From 48f7e163e634ed910c84d80cebf9f32833aeb3c8 Mon Sep 17 00:00:00 2001 From: bnnm Date: Mon, 3 Feb 2020 00:19:47 +0100 Subject: [PATCH 09/11] Fix .xwc v4 XMA2 [Syndicate (X360)] --- src/meta/xwc.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/meta/xwc.c b/src/meta/xwc.c index dcd1e4fa..3bd30ac2 100644 --- a/src/meta/xwc.c +++ b/src/meta/xwc.c @@ -74,17 +74,30 @@ VGMSTREAM * init_vgmstream_xwc(STREAMFILE *streamFile) { #ifdef VGM_USE_FFMPEG case 0x584D4100: { /* "XMA\0" (X360) */ uint8_t buf[0x100]; - int32_t bytes, seek_size, block_size, block_count, sample_rate; + int32_t bytes, seek_size, block_size, block_count, sample_rate, chunk_size; - seek_size = read_32bitLE(extra_offset+0x00, streamFile); - start_offset = extra_offset+0x04 + seek_size + read_32bitLE(extra_offset+0x04+seek_size, streamFile) + 0x08; + seek_size = read_32bitLE(extra_offset + 0x00, streamFile); + chunk_size = read_32bitLE(extra_offset + 0x04 + seek_size, streamFile); + + start_offset = extra_offset+ 0x04 + seek_size + chunk_size + 0x08; start_offset += (start_offset % 0x800) ? 0x800 - (start_offset % 0x800) : 0; /* padded */ data_size = data_size - start_offset; - sample_rate = read_32bitBE(extra_offset+0x04+seek_size+0x10, streamFile); - block_size = read_32bitBE(extra_offset+0x04+seek_size+0x1c, streamFile); - block_count = read_32bitBE(extra_offset+0x04+seek_size+0x28, streamFile); - /* others: scrambled RIFF fmt BE values */ + if (chunk_size == 0x34) { /* new XMA2 */ + sample_rate = read_32bitLE(extra_offset+0x04+seek_size+0x08, streamFile); + block_size = read_32bitLE(extra_offset+0x04+seek_size+0x20, streamFile); + block_count = data_size / block_size; + /* others: standard RIFF XMA2 fmt? */ + } + else if (chunk_size == 0x2c) { /* old XMA2 */ + sample_rate = read_32bitBE(extra_offset+0x04+seek_size+0x10, streamFile); + block_size = read_32bitBE(extra_offset+0x04+seek_size+0x1c, streamFile); + block_count = read_32bitBE(extra_offset+0x04+seek_size+0x28, streamFile); + /* others: scrambled RIFF fmt BE values */ + } + else { + goto fail; + } bytes = ffmpeg_make_riff_xma2(buf,0x100, vgmstream->num_samples, data_size, vgmstream->channels, sample_rate, block_count, block_size); vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,data_size); From 6a7e55f8ccdfe39d16df24f9739d5f38a1cbc284 Mon Sep 17 00:00:00 2001 From: bnnm Date: Mon, 3 Feb 2020 00:23:07 +0100 Subject: [PATCH 10/11] Add lrmh+lrmb [LocoRoco 2 (PSP), LocoRoco: Midnight Carnival (PSP)] --- src/formats.c | 2 + src/libvgmstream.vcproj | 8 ++ src/libvgmstream.vcxproj | 2 + src/libvgmstream.vcxproj.filters | 6 ++ src/meta/lrmd.c | 145 +++++++++++++++++++++++++++++++ src/meta/lrmd_streamfile.h | 19 ++++ src/meta/meta.h | 2 + src/vgmstream.c | 1 + src/vgmstream.h | 1 + 9 files changed, 186 insertions(+) create mode 100644 src/meta/lrmd.c create mode 100644 src/meta/lrmd_streamfile.h diff --git a/src/formats.c b/src/formats.c index 962f876f..543c066d 100644 --- a/src/formats.c +++ b/src/formats.c @@ -271,6 +271,7 @@ static const char* extension_list[] = { "lpcm", "lpk", "lps", + "lrmb", "lse", "lsf", "lstm", //fake extension for .stm @@ -1266,6 +1267,7 @@ static const meta_info meta_info_list[] = { {meta_FDA, "Relic FDA header"}, {meta_TGC, "Tiger Game.com .4 header"}, {meta_KWB, "Koei Tecmo WaveBank header"}, + {meta_LRMD, "Sony LRMD header"}, }; diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 37fe8667..9fce1468 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -288,6 +288,10 @@ RelativePath=".\meta\kma9_streamfile.h" > + + @@ -795,6 +799,10 @@ + + + @@ -178,6 +179,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 8c5ceafd..fa947f93 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -113,6 +113,9 @@ meta\Header Files + + meta\Header Files + meta\Header Files @@ -1678,6 +1681,9 @@ meta\Source Files + + meta\Source Files + meta\Source Files diff --git a/src/meta/lrmd.c b/src/meta/lrmd.c new file mode 100644 index 00000000..e53e9bfe --- /dev/null +++ b/src/meta/lrmd.c @@ -0,0 +1,145 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "lrmd_streamfile.h" + +/* LRMD - Sony/SCEI's format (Loco Roco Music Data?) [LocoRoco 2 (PSP), LocoRoco: Midnight Carnival (PSP)] */ +VGMSTREAM * init_vgmstream_lrmd(STREAMFILE *sf) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE * sf_h = NULL, *temp_sf = NULL; + off_t stream_offset, section1_offset, section2_offset, basename_offset, subname_offset; + size_t stream_size, layer_chunk; + int loop_flag, channel_count, sample_rate, layers; + int32_t num_samples, loop_start, loop_end; + int total_subsongs, target_subsong = sf->stream_index; + + + /* checks */ + if (!check_extensions(sf, "lrmb")) + goto fail; + sf_h = open_streamfile_by_ext(sf, "lrmh"); + if (!sf_h) goto fail; + + + if (read_u32be(0x00, sf_h) != 0x4C524D44) /* "LRMD" */ + goto fail; + /* 0x00: version 1? */ + /* 0x08: header size */ + /* 0x0c: body size */ + + if (read_u32be(0x10, sf_h) != 0x52455144) /* "REQD" */ + goto fail; + /* 0x14: chunk size */ + /* 0x18: null? */ + /* 0x1c: 1? */ + /* 0x20: null */ + basename_offset = read_u32le(0x24, sf_h); + if (read_u16le(0x28, sf_h) != 0x4000) { /* pitch? */ + VGM_LOG("LRMD: unknown value\n"); + goto fail; + } + layer_chunk = read_u16le(0x2a, sf_h); + num_samples = read_u32le(0x2c, sf_h); + /* 0x30: null? */ + /* 0x34: data size for all layers */ + layers = read_u32le(0x38, sf_h); + section1_offset = read_u32le(0x3c, sf_h); + /* 0x40: seek/layer? table entries */ + /* 0x44: seek/layer? table offset */ + /* 0x48: section2 flag */ + section2_offset = read_u32le(0x4c, sf_h); + /* 0x40: section3 flag */ + /* 0x44: section3 (unknown) */ + + total_subsongs = layers; + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + + /* data is divided into N interleaved layers sharing config, so it could be implemented as + * layered, but since they have names it's worth showing as subsongs */ + + /* section1: layer config */ + section1_offset += (target_subsong - 1) * 0x18; + /* 0x00: null */ + subname_offset = read_u32le(section1_offset + 0x04, sf_h); + /* 0x08: unk */ + /* 0x0c: flags? */ + /* 0x10: null? */ + /* 0x14: null? */ + sample_rate = 44100; + channel_count = 2; + + /* section2: loops */ + /* 0x00: offset to "loop" name */ + if (section2_offset > 0) { + loop_end = read_u32le(section2_offset + 0x04, sf_h); + loop_start = read_u32le(section2_offset + 0x08, sf_h); + loop_flag = read_u32le(section2_offset + 0x0c, sf_h); + } + else { + loop_end = 0; + loop_start = 0; + loop_flag = 0; + } + + + //TODO: LR2's muihouse has buggy 7-layer interleave + /* data de-interleave */ + temp_sf = setup_lrmd_streamfile(sf, layer_chunk / layers, (target_subsong-1), total_subsongs); + if (!temp_sf) goto fail; + + stream_offset = 0x00; + stream_size = get_streamfile_size(temp_sf); + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_LRMD; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + vgmstream->stream_size = stream_size; + vgmstream->num_streams = total_subsongs; + +#ifdef VGM_USE_FFMPEG + { + int block_align, encoder_delay; + + block_align = layer_chunk / layers; + encoder_delay = 1024; /* assumed */ + vgmstream->num_samples -= encoder_delay; + + vgmstream->codec_data = init_ffmpeg_atrac3_raw(temp_sf, stream_offset, stream_size, vgmstream->num_samples, vgmstream->channels, vgmstream->sample_rate, block_align, encoder_delay); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + } +#else + goto fail; +#endif + + /* name custom main + layer name */ + { + int name_len = read_string(vgmstream->stream_name, STREAM_NAME_SIZE - 1, basename_offset, sf_h); + + strcat(vgmstream->stream_name, "/"); + name_len++; + + read_string(vgmstream->stream_name + name_len, STREAM_NAME_SIZE - name_len, subname_offset, sf_h); + } + + if (!vgmstream_open_stream(vgmstream, temp_sf, stream_offset)) + goto fail; + + close_streamfile(sf_h); + close_streamfile(temp_sf); + return vgmstream; + +fail: + close_streamfile(sf_h); + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/lrmd_streamfile.h b/src/meta/lrmd_streamfile.h new file mode 100644 index 00000000..f46ce4c4 --- /dev/null +++ b/src/meta/lrmd_streamfile.h @@ -0,0 +1,19 @@ +#ifndef _LRMD_STREAMFILE_H_ +#define _LRMD_STREAMFILE_H_ +#include "deblock_streamfile.h" + +/* Deinterleaves LRMD streams */ +static STREAMFILE* setup_lrmd_streamfile(STREAMFILE *sf, size_t interleave_size, int stream_number, int stream_count) { + STREAMFILE *new_sf = NULL; + deblock_config_t cfg = {0}; + + cfg.chunk_size = interleave_size; + cfg.step_start = stream_number; + cfg.step_count = stream_count; + + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_deblock_streamfile_f(new_sf, &cfg); + return new_sf; +} + +#endif /* _LRMD_STREAMFILE_H_ */ diff --git a/src/meta/meta.h b/src/meta/meta.h index 63bbecd1..a78e2fed 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -885,4 +885,6 @@ VGMSTREAM * init_vgmstream_tgc(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_kwb(STREAMFILE* sf); +VGMSTREAM * init_vgmstream_lrmd(STREAMFILE* sf); + #endif /*_META_H*/ diff --git a/src/vgmstream.c b/src/vgmstream.c index 88c781d9..3d8d53c5 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -490,6 +490,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_fda, init_vgmstream_tgc, init_vgmstream_kwb, + init_vgmstream_lrmd, /* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */ init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */ diff --git a/src/vgmstream.h b/src/vgmstream.h index 3225f9dc..d270effa 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -722,6 +722,7 @@ typedef enum { meta_FDA, meta_TGC, meta_KWB, + meta_LRMD, } meta_t; /* standard WAVEFORMATEXTENSIBLE speaker positions */ From e6869e510953aa7a505122a278d7e9aa11a2b7bb Mon Sep 17 00:00:00 2001 From: bnnm Date: Mon, 3 Feb 2020 00:23:23 +0100 Subject: [PATCH 11/11] Update doc --- README.md | 7 ++++++- doc/TXTH.md | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 63216d29..422635d2 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ Help and newest builds can be found here: https://www.hcs64.com/ Latest development is usually here: https://github.com/losnoco/vgmstream/ +Latest releases are here: https://github.com/losnoco/vgmstream/releases +Automated builds with the latest changes: https://ci.appveyor.com/project/kode54/vgmstream/branch/master/artifacts + You can find further info about other details in https://github.com/losnoco/vgmstream/tree/master/doc ## Needed extra files (for Windows) @@ -423,6 +426,7 @@ are used in few games. - Circus XPCM ADPCM - OKI 4-bit ADPCM (16-bit output, 4-shift, PC-FX) - Ubisoft 4/6-bit ADPCM +- Tiger Game.com ADPCM - SDX2 2:1 Squareroot-Delta-Exact compression DPCM - CBD2 2:1 Cuberoot-Delta-Exact compression DPCM - Activision EXAKT SASSC DPCM @@ -430,11 +434,12 @@ are used in few games. - InterPlay ACM - VisualArt's NWA - Electronic Arts MicroTalk a.k.a. UTK or UMT +- Relic Codec - CRI HCA - Xiph Vorbis (Ogg, FSB5, Wwise, OGL, Silicon Knights) - MPEG MP1/2/3 (standard, AHX, XVAG, FSB, AWC, P3D, etc) - ITU-T G.722.1 annex C (Polycom Siren 14) -- ITU G.719 annex B (Polycom Siren 22) +- ITU-T G.719 annex B (Polycom Siren 22) - Electronic Arts EALayer3 - Electronic Arts EA-XMA - Sony ATRAC3, ATRAC3plus diff --git a/doc/TXTH.md b/doc/TXTH.md index ef4bbfda..96fab4cb 100644 --- a/doc/TXTH.md +++ b/doc/TXTH.md @@ -25,7 +25,7 @@ start_offset = 0x100 #data starts after exactly this value num_samples = data_size #find automatically number of samples in the file loop_flag = auto #find loop points in PS-ADPCM ``` -A text file with the above commands must be saved as `.vag.txth` or `.txth` (preferably the former), notice it starts with a "." (dot). On Windows files starting with a dot can be created by appending a dot at the end when renaming: `.txth.` +A text file with the above commands must be saved as `.vag.txth` or `.txth` (preferably the former), notice it starts with a "." (dot). On some Windows versions files starting with a dot need to be created by appending a dot at the end when renaming: `.txth.` While the main point is playing the file, many of TXTH's features are aimed towards keeping original data intact, for documentation and preservation purposes; try leaving data as untouched as possible and consider how the game plays the file, as there is a good chance some feature can mimic it.