Merge pull request #472 from bnnm/txtp-nub-mab

txtp nub mab
This commit is contained in:
bnnm 2019-09-15 15:07:31 +02:00 committed by GitHub
commit cadb9162dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1058 additions and 445 deletions

View File

@ -74,6 +74,7 @@ Custom STREAMFILEs wrapping base STREAMFILEs may be used for complex I/O cases:
- data needs decryption (`io_streamfile`)
- data must be expanded/reduced on the fly for codecs that are not easy to feed chunked data (`io_streamfile`)
- data is divided in multiple physical files, but must be read as a single (`multifile_streamfile`)
Certain metas combine those streamfiles together with special layouts to support very complex cases, that would require massive changes in vgmstream to support in a cleaner (possible undesirable) way.

View File

@ -58,11 +58,12 @@ The above may be combined with math operations (+-*/): `(key) = (number) (op) (o
### KEYS
#### CODEC [REQUIRED]
Sets codec used to encode the data. Accepted codec strings:
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, often +0x1000
# * 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)
@ -70,18 +71,19 @@ Sets codec used to encode the data. Accepted codec strings:
# * 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, often +0x1000
# * 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
# * 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
# * 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)
@ -110,13 +112,14 @@ Sets codec used to encode the data. Accepted codec strings:
# - 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 but varies)
# * 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
# * Should set skip_samples (more than 2048 but varies)
# 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
@ -291,7 +294,7 @@ 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 can set the separation per channel, usually 0x20 (16 values * 2 bytes). So channel N coefs are read at `coef_offset + coef_spacing * N`
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´.
@ -303,6 +306,22 @@ 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.

View File

@ -305,17 +305,23 @@ Possible operations:
- `Nu`: upmix (insert) N ('pushing' all following channels forward)
- `Nd`: downmix (remove) N ('pulling' all following channels backward)
- `ND`: downmix (remove) N and all following channels
- `N(type)(time-start)+(time-length)`: defines a fade
- `N(type)(position)(time-start)+(time-length)`: defines a fade
* `type` can be `{` = fade-in, `}` = fade-out, `(` = crossfade-in, `)` = crossfade-out
* crossfades are better tuned to use when changing between tracks
* `(position)` pre-adjusts `(time-start)` to start after certain time (optional)
* using multiple fades in the same channel will cancel previous fades
* may only cancel when fade is after previous one
* `}` then `{` makes sense, but `}` then `}` will make funny volume bumps
* `}` then `{` or `{` then `}` makes sense, but `}` then `}` will make funny volume bumps
* example: `1{0:10+0:5, 1}0:30+0:5` fades-in at 10 seconds, then fades-out at 30 seconds
- `N^(volume-start)~(volume-end)=(shape)@(time-pre)~(time-start)+(time-length)~(time-last)`: defines full fade envelope
* full definition of the above to allow precise volume changes over time
* not necessarily fades, as you could set length 0 for volume "bumps", or make volumes 1.0~0.5
* pre/post may be -1 to set "file start" and "file end", cancelled by next fade
* full definition of a fade envelope to allow precise volume changes over time
* not necessarily fades, as you could set length 0 for volume "bumps" like `1.0~0.5`
* `(shape)` can be `{` = fade, `(` = crossfade, other values are reserved for internal testing and may change anytime
* `(time-start)`+`(time-length)` make `(time-end)`
* between `(time-pre)` and `(time-start)` song uses `(volume-start)`
* between `(time-start)` and `(time-end)` song gradually changes `(volume-start)` to `(volume-end)` (depending on `(shape)`)
* between `(time-end)` and `(time-post)` song uses `(volume-end)`
* `time-pre/post` may be -1 to set "file start" and "file end", cancelled by next fade
Considering:
- `N` and `M` are channels (*current* value after previous operators are applied)
@ -324,6 +330,8 @@ Considering:
- may use `x` instead of `*` and `_` instead of `:` (for mini-TXTP)
- `(volume)` is a `N.N` decimal value where 1.0 is 100% base volume
- negative volume inverts the waveform (for weird effects)
- `(position)` can be `N.NL` or `NL` = N.N loops
- if loop start is 1000 and loop end 5000, `0.0L` = 1000 samples, `1.0L` = 5000 samples, `2.0L` = 9000 samples, etc
- `(time)` can be `N:NN(.n)` (minutes:seconds), `N.N` (seconds) or `N` (samples)
- represents the file's global play time, so it may be set after N loops
- beware of `10.0` (ten seconds) vs `10` (ten samples)
@ -449,7 +457,7 @@ TXTP may even reference other TXTP, or files that require TXTH, for extra comple
bgm bank#s2#c1,2
```
You may add spaces as needed (but try to keep it simple and don't go overboard), though commands *must* start with `#(command)` (`#(space)(anything)` is a comment). Commands without corresponding file are ignored too (seen as comments too), while incorrect commands are ignored and skip to next, though the parser may try to make something usable of them (this may be change anytime without warning):
You may add spaces as needed (but try to keep it simple and don't go overboard), though commands *must* start with `#(command)` (`#(space)(anything)` is a comment). Commands without corresponding file are ignored too (seen as comments), while incorrect commands are ignored and skip to next, though the parser may try to make something usable of them (this may be change anytime without warning):
```
# those are all equivalent
song#s2#c1,2
@ -510,17 +518,20 @@ Repeated commands overwrite previous setting, except comma-separated commands th
```
# overwrites, equivalent to #s2
song#s1#s2
```
```
# adds, equivalent to #m1-2,3-4,5-6
song#m1-2#m3-4
commands = #m5-6
# also added to song
commands = #l 3.0
```
The parser is fairly simplistic and lax, and may be erratic with edge cases or behave unexpectedly due to unforeseen use-cases and bugs. As filenames may contain spaces or #, certain name patterns could fool it too. Keep in mind this while making .txtp files.
## MINI-TXTP
To simplify TXTP creation, if the .txtp doesn't set a name inside its filename is used directly including config. Note that extension must be included (since vgmstream needs a full filename). You can set `commands` inside the .txtp too:
To simplify TXTP creation, if the .txtp doesn't set a name inside then its filename is used directly, including config. Note that extension must be included (since vgmstream needs a full filename). You can set `commands` inside the .txtp too:
- *bgm.sxd2#12.txtp*: plays subsong 12
- *bgm.sxd2#12.txtp*, , inside has `commands = #@volume 0.5`: plays subsong 12 at half volume
- *bgm.sxd2.txtp*, , inside has `commands = #12 #@volume 0.5`: plays subsong 12 at half volume
@ -563,7 +574,7 @@ song#m1+3,2+4,3D
song#m1+3*0.7,2+4*0.7,3D
# downmix 4ch layers to stereo with equal adjusted volume (common layer mixdown)
song#m0*0.7,m1*0.7,1+3*0.7,2+4*0.7,3D
song#m0*0.7,1*0.7,1+3*0.7,2+4*0.7,3D
# downmix stereo to mono (ignored if file is 1ch)
zelda-cdi.xa#m1d
@ -591,21 +602,21 @@ song#m1-2,2*0.5
# fade-in ch3+4 percussion track layer into main track, downmix to stereo
# (may be split in multiple lines, no difference)
okami-ryoshima_coast.aix#l2
commands = #m3(1:10~0:05 # loop happens after ~1:10
commands = #m4(1:10~0:05 # ch3/4 are percussion tracks
commands = #m3(1:10+0:05 # loop happens after ~1:10
commands = #m4(1:10+0:05 # ch3/4 are percussion tracks
commands = #m1+3*0.707,2+4*0.707 # ch3/4 always mixed but silent until 1:10
commands = #m3D # remove channels after all mixing
# same but fade-out percussion after second loop
okami-ryoshima_coast.aix#l3
commands = #m3(1:10~0:05,3)2:20~0:05
commands = #m4(1:10~0:05,4)2:20~0:05
commands = #m3(1:10+0:05,3)2:20+0:05
commands = #m4(1:10+0:05,4)2:20+0:05
commands = #m1+3*0.707,2+4*0.707,3D
# crossfade exploration and combat sections after loop
ffxiii-2~eclipse.aix
commands = #m1)1:50~0:10,m2)1:50~0:10
commands = #m3(1:50~0:10,m4(1:50~0:10
commands = #m1)1:50+0:10,2)1:50+0:10
commands = #m3(1:50+0:10,4(1:50+0:10
commands = #m1+3,2+4,3D # won't play at the same time, no volume needed
# ghetto voice removal (invert channel + other channel removes duplicated parts, and vocals are often layered)

View File

@ -330,6 +330,7 @@ static const char* extension_list[] = {
//"opus", //common
"opusx",
"otm",
"oto", //txth/reserved [Vampire Savior (SAT)]
"ovb",
"p04", //txth/reserved [Psychic Force 2012 (DC)]

View File

@ -316,6 +316,10 @@
RelativePath=".\meta\sqex_scd_streamfile.h"
>
</File>
<File
RelativePath=".\meta\sqex_sead_streamfile.h"
>
</File>
<File
RelativePath=".\meta\txth_streamfile.h"
>

View File

@ -121,6 +121,7 @@
<ClInclude Include="meta\opus_interleave_streamfile.h" />
<ClInclude Include="meta\sfh_streamfile.h" />
<ClInclude Include="meta\sqex_scd_streamfile.h" />
<ClInclude Include="meta\sqex_sead_streamfile.h" />
<ClInclude Include="meta\txth_streamfile.h" />
<ClInclude Include="meta\ubi_bao_streamfile.h" />
<ClInclude Include="meta\ubi_sb_streamfile.h" />

View File

@ -134,6 +134,9 @@
<ClInclude Include="meta\sqex_scd_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\sqex_sead_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\txth_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>

View File

@ -1,7 +1,7 @@
#include "meta.h"
/* BMP - from Jubeat Clan (AC) */
/* BMP - from Jubeat series (AC) */
VGMSTREAM * init_vgmstream_bmp_konami(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;

View File

@ -11,7 +11,7 @@ VGMSTREAM * init_vgmstream_nub(STREAMFILE *streamFile) {
off_t name_offset = 0;
size_t name_size = 0;
int total_subsongs, target_subsong = streamFile->stream_index;
uint32_t codec;
uint32_t version, codec;
const char* fake_ext;
VGMSTREAM*(*init_vgmstream_function)(STREAMFILE *) = NULL;
char name[STREAM_NAME_SIZE] = {0};
@ -21,13 +21,17 @@ VGMSTREAM * init_vgmstream_nub(STREAMFILE *streamFile) {
/* checks */
if (!check_extensions(streamFile, "nub"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x00020100) /* v2.1? */
version = read_32bitBE(0x00,streamFile);
if (version != 0x00020000 && /* v2.0 (rare, ex. Ridge Race 6 (X360)) */
version != 0x00020100 && /* v2.1 (common) */
version != 0x01020100) /* same but LE? (seen in PSP games, not PS4) */
goto fail;
if (read_32bitBE(0x04,streamFile) != 0x00000000) /* null */
goto fail;
/* ToV PS4 uses LE */
if (guess_endianness32bit(0x08, streamFile)) {
/* sometimes LE [Soul Calibur: Broken Destiny (PSP), Tales of Vesperia (PS4) */
if (guess_endianness32bit(0x10, streamFile)) {
read_32bit = read_32bitBE;
} else{
read_32bit = read_32bitLE;
@ -40,13 +44,18 @@ VGMSTREAM * init_vgmstream_nub(STREAMFILE *streamFile) {
size_t header_size, subheader_size, stream_size;
/* - base header */
/* 0x08: file id/number */
/* 0x08: file id/number (can be 0 = first) */
total_subsongs = read_32bit(0x0c, streamFile); /* .nub with 0 files do exist */
data_start = read_32bit(0x10, streamFile);
/* 0x14: data end */
data_start = read_32bit(0x10, streamFile); /* exists even with 0 files */
/* 0x14: data end (may have padding) */
header_start = read_32bit(0x18, streamFile);
/* 0x1c: header end */
/* probably means "header end" in v2.0 */
if (version == 0x00020000) {
data_start = align_size_to_block(data_start, 0x800);
}
if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
@ -54,25 +63,37 @@ VGMSTREAM * init_vgmstream_nub(STREAMFILE *streamFile) {
/* .nus have all headers first then all data, but extractors often just paste them together,
* so we'll combine header+data on the fly to make them playable with existing parsers.
* As formats inside .nub don't exist as external files, they can be extracted in various
* ways so we'll try to match (though BNSF can be found as header+data in some bigfiles too). */
* Formats inside .nub don't exist as external files, so they could be extracted in various
* ways that we'll try to match (though BNSF can be found as header+data in some bigfiles too). */
header_offset = offset;
/* - extension (as referenced in companion files with internal filenames, ex. "BGM_MovingDemo1.is14" > "is14") */
if (version != 0x00020000)
offset += 0x04; /* skip but not in v2.0 */
/* - file header */
/* 00: extension (as referenced in companion files with internal filenames, ex. "BGM_MovingDemo1.is14" > "is14") */
/* 04: config? */
/* 08: header id/number */
codec = (uint32_t)read_32bit(offset + 0x0c, streamFile);
/* 10: null */
stream_size = read_32bit(offset + 0x14, streamFile); /* 0x10 aligned */
stream_offset = read_32bit(offset + 0x18, streamFile) + data_start;
subheader_size = read_32bit(offset + 0x1c, streamFile);
/* 0x00: config? */
/* 0x04: header id/number */
codec = (uint32_t)read_32bit(offset + 0x08, streamFile);
/* 0x0c: null */
stream_size = read_32bit(offset + 0x10, streamFile); /* 0x10 aligned */
stream_offset = read_32bit(offset + 0x14, streamFile) + data_start;
subheader_size = read_32bit(offset + 0x18, streamFile);
/* rest looks like config/volumes/etc */
subheader_start = 0xBC;
header_offset = offset;
if (version == 0x00020000)
subheader_start = 0xAC;
else
subheader_start = 0xBC;
header_size = align_size_to_block(subheader_start + subheader_size, 0x10);
switch(codec) {
case 0x00: /* (none) (xma1) */
fake_ext = "xma";
init_vgmstream_function = init_vgmstream_nub_xma;
break;
case 0x01: /* "wav\0" */
fake_ext = "wav";
init_vgmstream_function = init_vgmstream_nub_wav;
@ -88,8 +109,8 @@ VGMSTREAM * init_vgmstream_nub(STREAMFILE *streamFile) {
init_vgmstream_function = init_vgmstream_nub_at3;
break;
case 0x04: /* "xma\0" (old) */
case 0x08: /* "xma\0" (new) */
case 0x04: /* "xma\0" (xma2 old) */
case 0x08: /* "xma\0" (xma2 new) */
fake_ext = "xma";
init_vgmstream_function = init_vgmstream_nub_xma;
break;
@ -110,7 +131,7 @@ VGMSTREAM * init_vgmstream_nub(STREAMFILE *streamFile) {
goto fail;
}
;VGM_LOG("NUB: subfile offset=%lx + %x\n", stream_offset, stream_size);
//;VGM_LOG("NUB: subfile header=%lx + %x, offset=%lx + %x\n", header_offset, header_size, stream_offset, stream_size);
temp_streamFile = make_nub_streamfile(streamFile, header_offset, header_size, stream_offset, stream_size, fake_ext);
if (!temp_streamFile) goto fail;
@ -148,6 +169,7 @@ VGMSTREAM * init_vgmstream_nub(STREAMFILE *streamFile) {
vgmstream = init_vgmstream_function(temp_streamFile);
if (!vgmstream) goto fail;
vgmstream->stream_size = get_streamfile_size(temp_streamFile);
vgmstream->num_streams = total_subsongs;
if (name[0] != '\0')
strcpy(vgmstream->stream_name, name);
@ -325,11 +347,12 @@ fail:
return NULL;
}
/* .nub xma - from Namco NUB archives [Tekken 6 (X360), Galaga Legions DX (X360)] */
/* .nub xma - from Namco NUB archives [Ridge Racer 6 (X360), Tekken 6 (X360), Galaga Legions DX (X360)] */
VGMSTREAM * init_vgmstream_nub_xma(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset, chunk_offset;
size_t data_size, chunk_size;
size_t data_size, chunk_size, header_size;
int loop_flag, channel_count, sample_rate, nus_codec;
int num_samples, loop_start_sample, loop_end_sample;
@ -337,25 +360,66 @@ VGMSTREAM * init_vgmstream_nub_xma(STREAMFILE *streamFile) {
/* checks */
if (!check_extensions(streamFile,"xma"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x786D6100) /* "xma\0" */
goto fail;
/* header with a "XMA2" or "fmt " chunk inside */
nus_codec = read_32bitBE(0x0C,streamFile);
data_size = read_32bitBE(0x14,streamFile);
chunk_offset = 0xBC;
chunk_size = read_32bitBE(0x24,streamFile);
if (nus_codec == 0x4) { /* "XMA2" */
xma2_parse_xma2_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample);
} else if (nus_codec == 0x8) { /* "fmt " */
channel_count = read_16bitBE(chunk_offset+0x02,streamFile);
sample_rate = read_32bitBE(chunk_offset+0x04,streamFile);
xma2_parse_fmt_chunk_extra(streamFile, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 1);
} else {
if (read_32bitBE(0x00,streamFile) == 0x786D6100) { /* "xma\0" */
/* nub v2.1 */
nus_codec = read_32bitBE(0x0C,streamFile);
data_size = read_32bitBE(0x14,streamFile);
header_size = read_32bitBE(0x1c,streamFile);
chunk_offset = 0xBC;
chunk_size = read_32bitBE(0x24,streamFile);
}
else if (read_32bitBE(0x08,streamFile) == 0 && read_32bitBE(0x0c,streamFile) == 0) {
/* nub v2.0 from Ridge Racer 6 */
nus_codec = read_32bitBE(0x08,streamFile);
data_size = read_32bitBE(0x10,streamFile);
header_size = read_32bitBE(0x18,streamFile);
chunk_offset = 0xAC;
chunk_size = header_size;
}
else {
goto fail;
}
start_offset = 0x100;
start_offset = align_size_to_block(chunk_offset + header_size, 0x10);
if (nus_codec == 0x00) { /* XMA1 "fmt " */
int loop_start_b, loop_end_b, loop_subframe;
xma1_parse_fmt_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &loop_start_b, &loop_end_b, &loop_subframe, 1);
{
ms_sample_data msd = {0};
msd.xma_version = 1;
msd.channels = channel_count;
msd.data_offset = start_offset;
msd.data_size = data_size;
msd.loop_flag = loop_flag;
msd.loop_start_b= loop_start_b;
msd.loop_end_b = loop_end_b;
msd.loop_start_subframe = loop_subframe & 0xF; /* lower 4b: subframe where the loop starts, 0..4 */
msd.loop_end_subframe = loop_subframe >> 4; /* upper 4b: subframe where the loop ends, 0..3 */
msd.chunk_offset= chunk_offset;
xma_get_samples(&msd, streamFile);
num_samples = msd.num_samples;
loop_start_sample = msd.loop_start_sample;
loop_end_sample = msd.loop_end_sample;
}
}
else if (nus_codec == 0x04) { /* "XMA2" */
xma2_parse_xma2_chunk(streamFile, chunk_offset, &channel_count,&sample_rate, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample);
}
else if (nus_codec == 0x08) { /* XMA2 "fmt " */
channel_count = read_16bitBE(chunk_offset+0x02,streamFile);
sample_rate = read_32bitBE(chunk_offset+0x04,streamFile);
xma2_parse_fmt_chunk_extra(streamFile, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 1);
}
else {
goto fail;
}
/* build the VGMSTREAM */
@ -373,9 +437,9 @@ VGMSTREAM * init_vgmstream_nub_xma(STREAMFILE *streamFile) {
uint8_t buf[0x100];
size_t bytes;
if (nus_codec == 0x4) { /* "XMA2" */
if (nus_codec == 0x04) {
bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile);
} else { /* "fmt " */
} else {
bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, chunk_offset,chunk_size, data_size, streamFile, 1);
}
vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,data_size);
@ -450,7 +514,7 @@ VGMSTREAM * init_vgmstream_nub_is14(STREAMFILE *streamFile) {
VGMSTREAM *vgmstream = NULL;
STREAMFILE *temp_streamFile = NULL;
off_t header_offset, stream_offset;
size_t header_size, stream_size;
size_t header_size, stream_size, sdat_size;
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
@ -460,21 +524,27 @@ VGMSTREAM * init_vgmstream_nub_is14(STREAMFILE *streamFile) {
if (read_32bitBE(0x00,streamFile) != 0x69733134) /* "is14" */
goto fail;
if (guess_endianness32bit(0x04, streamFile)) {
if (guess_endianness32bit(0x1c, streamFile)) {
read_32bit = read_32bitBE;
} else{
read_32bit = read_32bitLE;
}
/* paste header+data together and pass to meta */
header_offset = 0xBC;
header_size = read_32bit(0x1c, streamFile);
/* size at 0x14 is padded, find "sdat" size BE (may move around) */
if (!find_chunk_riff_be(streamFile, 0x73646174, 0xbc+0x0c, header_size - 0x0c, NULL, &sdat_size))
goto fail;
stream_offset = align_size_to_block(header_offset + header_size, 0x10);
stream_size = read_32bitBE(header_offset + header_size - 0x04, streamFile); /* size at 0x14 is padded, use "sdat" size BE */
VGM_LOG("%lx, %x, %lx, %x\n", header_offset, header_size, stream_offset, stream_size);
stream_size = sdat_size;
temp_streamFile = make_nub_streamfile(streamFile, header_offset, header_size, stream_offset, stream_size, "bnsf");
if (!temp_streamFile) goto fail;
dump_streamfile(temp_streamFile, 0);
vgmstream = init_vgmstream_bnsf(temp_streamFile);
if (!vgmstream) goto fail;

View File

@ -215,7 +215,6 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
if (is_ogg) {
if (read_32bitBE(0x00,streamFile) == 0x2c444430) { /* Psychic Software [Darkwind: War on Wheels (PC)] */
ovmi.decryption_callback = psychic_ogg_decryption_callback;
ovmi.meta_type = meta_OGG_encrypted;
}
else if (read_32bitBE(0x00,streamFile) == 0x4C325344) { /* "L2SD" instead of "OggS" [Lineage II Chronicle 4 (PC)] */
cfg.is_header_swap = 1;
@ -234,7 +233,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
cfg.is_encrypted = 1;
}
else if (read_32bitBE(0x00,streamFile) == 0x4f676753) { /* "OggS" (standard) */
ovmi.meta_type = meta_OGG_VORBIS;
;
}
else {
goto fail; /* unknown/not Ogg Vorbis (ex. Wwise) */
@ -245,7 +244,6 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
if (read_32bitBE(0x00,streamFile) != 0x4f676753) { /* "OggS" (optionally encrypted) */
ovmi.decryption_callback = um3_ogg_decryption_callback;
}
ovmi.meta_type = meta_OGG_encrypted;
}
if (is_kovs) { /* Koei Tecmo PC games */
@ -371,7 +369,6 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
goto fail;
}
ovmi.decryption_callback = rpgmvo_ogg_decryption_callback;
ovmi.meta_type = meta_OGG_encrypted;
start_offset = 0x10;
}
@ -437,13 +434,19 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) {
}
}
if (cfg.is_encrypted) {
ovmi.meta_type = meta_OGG_encrypted;
if (cfg.is_encrypted) {
temp_streamFile = setup_ogg_vorbis_streamfile(streamFile, cfg);
if (!temp_streamFile) goto fail;
}
if (ovmi.meta_type == 0) {
if (cfg.is_encrypted || ovmi.decryption_callback != NULL)
ovmi.meta_type = meta_OGG_encrypted;
else
ovmi.meta_type = meta_OGG_VORBIS;
}
vgmstream = init_vgmstream_ogg_vorbis_callbacks(temp_streamFile != NULL ? temp_streamFile : streamFile, NULL, start_offset, &ovmi);
close_streamfile(temp_streamFile);

View File

@ -149,7 +149,7 @@ static int read_fmt(int big_endian, STREAMFILE * streamFile, off_t current_chunk
fmt->interleave = 0x02;
break;
case 8:
fmt->coding_type = coding_PCM8_U_int;
fmt->coding_type = coding_PCM8_U;
fmt->interleave = 0x01;
break;
default:
@ -556,7 +556,7 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
vgmstream->num_samples = pcm_bytes_to_samples(data_size, fmt.channel_count, 16);
break;
case coding_PCM8_U_int:
case coding_PCM8_U:
vgmstream->num_samples = pcm_bytes_to_samples(data_size, vgmstream->channels, 8);
break;
@ -899,7 +899,7 @@ VGMSTREAM * init_vgmstream_rifx(STREAMFILE *streamFile) {
case coding_PCM16BE:
vgmstream->num_samples = pcm_bytes_to_samples(data_size, vgmstream->channels, 16);
break;
case coding_PCM8_U_int:
case coding_PCM8_U:
vgmstream->num_samples = pcm_bytes_to_samples(data_size, vgmstream->channels, 8);
break;
default:

View File

@ -1,186 +1,137 @@
#include "meta.h"
#include "../coding/coding.h"
#include "sqex_sead_streamfile.h"
static STREAMFILE* setup_sead_hca_streamfile(STREAMFILE *streamFile, off_t subfile_offset, size_t subfile_size, int encryption, size_t header_size, size_t key_start);
typedef struct {
int big_endian;
int version;
int is_sab;
int is_mab;
int total_subsongs;
int target_subsong;
uint16_t wave_id;
int loop_flag;
int channel_count;
int codec;
int sample_rate;
int loop_start;
int loop_end;
off_t meta_offset;
off_t extradata_offset;
size_t extradata_size;
size_t stream_size;
size_t special_size;
off_t descriptor_offset;
size_t descriptor_size;
off_t filename_offset;
size_t filename_size;
off_t cuename_offset;
size_t cuename_size;
off_t modename_offset;
size_t modename_size;
off_t instname_offset;
size_t instname_size;
off_t sndname_offset;
size_t sndname_size;
off_t sections_offset;
off_t snd_offset;
off_t trk_offset;
off_t musc_offset;
off_t inst_offset;
off_t mtrl_offset;
char readable_name[STREAM_NAME_SIZE];
} sead_header;
static int parse_sead(sead_header *sead, STREAMFILE *sf);
/* SABF/MABF - Square Enix's "sead" audio games [Dragon Quest Builders (PS3), Dissidia Opera Omnia (mobile), FF XV (PS4)] */
VGMSTREAM * init_vgmstream_sqex_sead(STREAMFILE * streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset, tables_offset, mtrl_offset, meta_offset, extradata_offset; //, info_offset, name_offset = 0;
size_t stream_size, descriptor_size, extradata_size, special_size; //, name_size = 0;
int loop_flag = 0, channel_count, codec, sample_rate, loop_start, loop_end;
int is_sab = 0, is_mab = 0;
int total_subsongs, target_subsong = streamFile->stream_index;
sead_header sead = {0};
off_t start_offset;
int target_subsong = streamFile->stream_index;
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
/* check extensions (.sab: sound/bgm, .mab: music, .sbin: Dissidia Opera Omnia .sab) */
if ( !check_extensions(streamFile,"sab,mab,sbin"))
/* checks */
/* .sab: sound/bgm
* .mab: music
* .sbin: Dissidia Opera Omnia .sab */
if (!check_extensions(streamFile,"sab,mab,sbin"))
goto fail;
/** main header **/
if (read_32bitBE(0x00,streamFile) == 0x73616266) { /* "sabf" */
is_sab = 1;
sead.is_sab = 1;
} else if (read_32bitBE(0x00,streamFile) == 0x6D616266) { /* "mabf" */
is_mab = 1;
sead.is_mab = 1;
} else {
/* there are other SEAD files with other chunks but similar formats too */
goto fail;
}
//if (read_8bit(0x04,streamFile) != 0x02) /* version? */
// goto fail;
/* 0x04(1): version? (usually 0x02, rarely 0x01, ex FF XV title) */
/* 0x05(1): 0x00/01? */
/* 0x06(2): version? (usually 0x10, rarely 0x20) */
if (read_16bitBE(0x06,streamFile) < 0x100) { /* use some value as no apparent flag */
sead.big_endian = guess_endianness16bit(0x06,streamFile); /* use some value as no apparent flag */
if (sead.big_endian) {
read_32bit = read_32bitBE;
read_16bit = read_16bitBE;
} else {
read_32bit = read_32bitLE;
read_16bit = read_16bitLE;
}
/* 0x08(1): version 0x04?, 0x0a(2): ? */
descriptor_size = read_8bit(0x09,streamFile);
if (read_32bit(0x0c,streamFile) != get_streamfile_size(streamFile))
sead.target_subsong = target_subsong;
if (!parse_sead(&sead, streamFile))
goto fail;
/* 0x10(n): file descriptor ("BGM", "Music", "SE", etc, long names are ok), padded */
tables_offset = 0x10 + (descriptor_size + 0x01); /* string null seems counted for padding */
if (tables_offset % 0x10)
tables_offset += 0x10 - (tables_offset % 0x10);
/** offset tables **/
if (is_sab) {
if (read_32bitBE(tables_offset+0x00,streamFile) != 0x736E6420) goto fail; /* "snd " (info) */
if (read_32bitBE(tables_offset+0x10,streamFile) != 0x73657120) goto fail; /* "seq " (unknown) */
if (read_32bitBE(tables_offset+0x20,streamFile) != 0x74726B20) goto fail; /* "trk " (unknown) */
if (read_32bitBE(tables_offset+0x30,streamFile) != 0x6D74726C) goto fail; /* "mtrl" (headers/streams) */
//info_offset = read_32bit(tables_offset+0x08,streamFile);
//seq_offset = read_32bit(tables_offset+0x18,streamFile);
//trk_offset = read_32bit(tables_offset+0x28,streamFile);
mtrl_offset = read_32bit(tables_offset+0x38,streamFile);
}
else if (is_mab) {
if (read_32bitBE(tables_offset+0x00,streamFile) != 0x6D757363) goto fail; /* "musc" (info) */
if (read_32bitBE(tables_offset+0x10,streamFile) != 0x696E7374) goto fail; /* "inst" (unknown) */
if (read_32bitBE(tables_offset+0x20,streamFile) != 0x6D74726C) goto fail; /* "mtrl" (headers/streams) */
//info_offset = read_32bit(tables_offset+0x08,streamFile);
//inst_offset = read_32bit(tables_offset+0x18,streamFile);
mtrl_offset = read_32bit(tables_offset+0x28,streamFile);
}
else {
goto fail;
}
/* each section starts with:
* 0x00(2): 0x00/01?, 0x02: size? (0x10), 0x04(2): entries, 0x06+: padded to 0x10
* 0x10+0x04*entry: offset from section start, also padded to 0x10 at the end */
/* find meta_offset in mtrl and total subsongs */
{
int i;
int entries = read_16bit(mtrl_offset+0x04,streamFile);
off_t entries_offset = mtrl_offset + 0x10;
if (target_subsong == 0) target_subsong = 1;
total_subsongs = 0;
meta_offset = 0;
/* manually find subsongs as entries can be dummy (ex. sfx banks in Dissidia Opera Omnia) */
for (i = 0; i < entries; i++) {
off_t entry_offset = mtrl_offset + read_32bit(entries_offset + i*0x04,streamFile);
if (read_8bit(entry_offset+0x05,streamFile) == 0)
continue; /* codec 0 when dummy */
total_subsongs++;
if (!meta_offset && total_subsongs == target_subsong)
meta_offset = entry_offset;
}
if (meta_offset == 0) goto fail;
/* SAB can contain 0 entries too */
}
/** stream header **/
/* 0x00(2): 0x00/01? */
/* 0x02(2): base entry size? (0x20) */
channel_count = read_8bit(meta_offset+0x04,streamFile);
codec = read_8bit(meta_offset+0x05,streamFile);
//entry_id = read_16bit(meta_offset+0x06,streamFile);
sample_rate = read_32bit(meta_offset+0x08,streamFile);
loop_start = read_32bit(meta_offset+0x0c,streamFile); /* in samples but usually ignored */
loop_end = read_32bit(meta_offset+0x10,streamFile);
extradata_size = read_32bit(meta_offset+0x14,streamFile); /* including subfile header, can be 0 */
stream_size = read_32bit(meta_offset+0x18,streamFile); /* not including subfile header */
special_size = read_32bit(meta_offset+0x1c,streamFile);
loop_flag = (loop_end > 0);
extradata_offset = meta_offset + 0x20;
/** info section (get stream name) **/
//if (is_sab) { //todo load name based on entry id
/* "snd " */
/* 0x08(2): file number within descriptor */
/* 0x1a(2): base_entry size (-0x10?) */
//name_size = read_32bit(snd_offset+0x20,streamFile);
//name_offset = snd_offset+0x70;
/* 0x24(4): unique id? (referenced in "seq" section) */
//}
//else if (is_mab) {
/* "musc" */
//looks like a "music cue" section, pointing to one subsection per "material".
// ex. one cue may point to 3 named subsongs/sections.
// some common header info from all materials is repeated (ex. sample rate), while other
// (loops, maybe proper num_samples) are listed per material but don't always match thei header
//}
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
vgmstream = allocate_vgmstream(sead.channel_count, sead.loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = sample_rate;
vgmstream->num_streams = total_subsongs;
vgmstream->stream_size = stream_size;
vgmstream->meta_type = is_sab ? meta_SQEX_SAB : meta_SQEX_MAB;
vgmstream->meta_type = sead.is_sab ? meta_SQEX_SAB : meta_SQEX_MAB;
vgmstream->sample_rate = sead.sample_rate;
vgmstream->num_streams = sead.total_subsongs;
vgmstream->stream_size = sead.stream_size;
strcpy(vgmstream->stream_name, sead.readable_name);
switch(codec) {
switch(sead.codec) {
case 0x01: { /* PCM [Chrono Trigger sfx (PC)] */
start_offset = extradata_offset + extradata_size;
start_offset = sead.extradata_offset + sead.extradata_size;
vgmstream->coding_type = coding_PCM16LE;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x02;
vgmstream->num_samples = pcm_bytes_to_samples(stream_size, vgmstream->channels, 16);
vgmstream->loop_start_sample = loop_start;
vgmstream->loop_end_sample = loop_end;
vgmstream->num_samples = pcm_bytes_to_samples(sead.stream_size, vgmstream->channels, 16);
vgmstream->loop_start_sample = sead.loop_start;
vgmstream->loop_end_sample = sead.loop_end;
break;
}
case 0x02: { /* MSADPCM [Dragon Quest Builders (Vita) sfx] */
start_offset = extradata_offset + extradata_size;
start_offset = sead.extradata_offset + sead.extradata_size;
/* 0x00 (2): null?, 0x02(2): entry size? */
vgmstream->coding_type = coding_MSADPCM;
vgmstream->layout_type = layout_none;
vgmstream->interleave_block_size = read_16bit(extradata_offset+0x04,streamFile);
vgmstream->interleave_block_size = read_16bit(sead.extradata_offset+0x04,streamFile);
/* much like AKBs, there are slightly different loop values here, probably more accurate
* (if no loop, loop_end doubles as num_samples) */
vgmstream->num_samples = msadpcm_bytes_to_samples(stream_size, vgmstream->interleave_block_size, vgmstream->channels);
vgmstream->loop_start_sample = read_32bit(extradata_offset+0x08, streamFile); //loop_start
vgmstream->loop_end_sample = read_32bit(extradata_offset+0x0c, streamFile); //loop_end
vgmstream->num_samples = msadpcm_bytes_to_samples(sead.stream_size, vgmstream->interleave_block_size, vgmstream->channels);
vgmstream->loop_start_sample = read_32bit(sead.extradata_offset+0x08, streamFile); //loop_start
vgmstream->loop_end_sample = read_32bit(sead.extradata_offset+0x0c, streamFile); //loop_end
break;
}
@ -188,17 +139,18 @@ VGMSTREAM * init_vgmstream_sqex_sead(STREAMFILE * streamFile) {
case 0x03: { /* OGG [Final Fantasy XV Benchmark sfx (PC)] */
VGMSTREAM *ogg_vgmstream = NULL;
ogg_vorbis_meta_info_t ovmi = {0};
off_t subfile_offset = extradata_offset + extradata_size;
off_t subfile_offset = sead.extradata_offset + sead.extradata_size;
ovmi.meta_type = vgmstream->meta_type;
ovmi.total_subsongs = total_subsongs;
ovmi.stream_size = stream_size;
ovmi.total_subsongs = sead.total_subsongs;
ovmi.stream_size = sead.stream_size;
/* post header has some kind of repeated values, config/table? */
ogg_vgmstream = init_vgmstream_ogg_vorbis_callbacks(streamFile, NULL, subfile_offset, &ovmi);
if (ogg_vgmstream) {
ogg_vgmstream->num_streams = vgmstream->num_streams;
ogg_vgmstream->stream_size = vgmstream->stream_size;
strcpy(ogg_vgmstream->stream_name, vgmstream->stream_name);
close_vgmstream(vgmstream);
return ogg_vgmstream;
@ -215,21 +167,21 @@ VGMSTREAM * init_vgmstream_sqex_sead(STREAMFILE * streamFile) {
case 0x04: { /* ATRAC9 [Dragon Quest Builders (Vita), Final Fantaxy XV (PS4)] */
atrac9_config cfg = {0};
start_offset = extradata_offset + extradata_size;
start_offset = sead.extradata_offset + sead.extradata_size;
/* post header has various typical ATRAC9 values */
cfg.channels = vgmstream->channels;
cfg.config_data = read_32bit(extradata_offset+0x0c,streamFile);
cfg.encoder_delay = read_32bit(extradata_offset+0x18,streamFile);
cfg.config_data = read_32bit(sead.extradata_offset+0x0c,streamFile);
cfg.encoder_delay = read_32bit(sead.extradata_offset+0x18,streamFile);
vgmstream->codec_data = init_atrac9(&cfg);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_ATRAC9;
vgmstream->layout_type = layout_none;
vgmstream->sample_rate = read_32bit(extradata_offset+0x1c,streamFile); /* SAB's sample rate can be different but it's ignored */
vgmstream->num_samples = read_32bit(extradata_offset+0x10,streamFile); /* loop values above are also weird and ignored */
vgmstream->loop_start_sample = read_32bit(extradata_offset+0x20, streamFile) - (loop_flag ? cfg.encoder_delay : 0); //loop_start
vgmstream->loop_end_sample = read_32bit(extradata_offset+0x24, streamFile) - (loop_flag ? cfg.encoder_delay : 0); //loop_end
vgmstream->sample_rate = read_32bit(sead.extradata_offset+0x1c,streamFile); /* SAB's sample rate can be different but it's ignored */
vgmstream->num_samples = read_32bit(sead.extradata_offset+0x10,streamFile); /* loop values above are also weird and ignored */
vgmstream->loop_start_sample = read_32bit(sead.extradata_offset+0x20, streamFile) - (sead.loop_flag ? cfg.encoder_delay : 0); //loop_start
vgmstream->loop_end_sample = read_32bit(sead.extradata_offset+0x24, streamFile) - (sead.loop_flag ? cfg.encoder_delay : 0); //loop_end
break;
}
#endif
@ -239,7 +191,7 @@ VGMSTREAM * init_vgmstream_sqex_sead(STREAMFILE * streamFile) {
mpeg_codec_data *mpeg_data = NULL;
mpeg_custom_config cfg = {0};
start_offset = extradata_offset + extradata_size;
start_offset = sead.extradata_offset + sead.extradata_size;
/* post header is a proper MSF, but sample rate/loops are ignored in favor of SAB's */
mpeg_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_STANDARD, &cfg);
@ -247,9 +199,9 @@ VGMSTREAM * init_vgmstream_sqex_sead(STREAMFILE * streamFile) {
vgmstream->codec_data = mpeg_data;
vgmstream->layout_type = layout_none;
vgmstream->num_samples = mpeg_bytes_to_samples(stream_size, mpeg_data);
vgmstream->loop_start_sample = loop_start;
vgmstream->loop_end_sample = loop_end;
vgmstream->num_samples = mpeg_bytes_to_samples(sead.stream_size, mpeg_data);
vgmstream->loop_start_sample = sead.loop_start;
vgmstream->loop_end_sample = sead.loop_end;
break;
}
#endif
@ -258,16 +210,16 @@ VGMSTREAM * init_vgmstream_sqex_sead(STREAMFILE * streamFile) {
//todo there is no easy way to use the HCA decoder; try subfile hack for now
VGMSTREAM *temp_vgmstream = NULL;
STREAMFILE *temp_streamFile = NULL;
off_t subfile_offset = extradata_offset + 0x10;
size_t subfile_size = stream_size + extradata_size - 0x10;
off_t subfile_offset = sead.extradata_offset + 0x10;
size_t subfile_size = sead.stream_size + sead.extradata_size - 0x10;
/* post header: values from the HCA header, in file endianness + HCA header */
size_t key_start = special_size & 0xff;
size_t header_size = read_16bit(extradata_offset+0x02, streamFile);
int encryption = read_16bit(extradata_offset+0x0c, streamFile); //maybe 8bit?
size_t key_start = sead.special_size & 0xff;
size_t header_size = read_16bit(sead.extradata_offset+0x02, streamFile);
int encryption = read_16bit(sead.extradata_offset+0x0c, streamFile); //maybe 8bit?
/* encryption type 0x01 found in Final Fantasy XII TZA (PS4/PC) */
temp_streamFile = setup_sead_hca_streamfile(streamFile, subfile_offset, subfile_size, encryption, header_size, key_start);
temp_streamFile = setup_sqex_sead_streamfile(streamFile, subfile_offset, subfile_size, encryption, header_size, key_start);
if (!temp_streamFile) goto fail;
temp_vgmstream = init_vgmstream_hca(temp_streamFile);
@ -276,6 +228,7 @@ VGMSTREAM * init_vgmstream_sqex_sead(STREAMFILE * streamFile) {
temp_vgmstream->num_streams = vgmstream->num_streams;
temp_vgmstream->stream_size = vgmstream->stream_size;
temp_vgmstream->meta_type = vgmstream->meta_type;
strcpy(temp_vgmstream->stream_name, vgmstream->stream_name);
close_streamfile(temp_streamFile);
close_vgmstream(vgmstream);
@ -289,10 +242,12 @@ VGMSTREAM * init_vgmstream_sqex_sead(STREAMFILE * streamFile) {
case 0x00: /* dummy entry */
default:
VGM_LOG("SQEX SEAD: unknown codec %x\n", codec);
VGM_LOG("SQEX SEAD: unknown codec %x\n", sead.codec);
goto fail;
}
strcpy(vgmstream->stream_name, sead.readable_name);
/* open the file for reading */
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
goto fail;
@ -304,79 +259,399 @@ fail:
}
typedef struct {
size_t header_size;
size_t key_start;
} sead_decryption_data;
static void build_readable_name(char * buf, size_t buf_size, sead_header *sead, STREAMFILE *sf) {
/* Encrypted HCA */
static size_t sead_decryption_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, sead_decryption_data* data) {
/* Found in FFXII_TZA.exe (same key in SCD Ogg V3) */
static const uint8_t encryption_key[0x100] = {
0x3A,0x32,0x32,0x32,0x03,0x7E,0x12,0xF7,0xB2,0xE2,0xA2,0x67,0x32,0x32,0x22,0x32, // 00-0F
0x32,0x52,0x16,0x1B,0x3C,0xA1,0x54,0x7B,0x1B,0x97,0xA6,0x93,0x1A,0x4B,0xAA,0xA6, // 10-1F
0x7A,0x7B,0x1B,0x97,0xA6,0xF7,0x02,0xBB,0xAA,0xA6,0xBB,0xF7,0x2A,0x51,0xBE,0x03, // 20-2F
0xF4,0x2A,0x51,0xBE,0x03,0xF4,0x2A,0x51,0xBE,0x12,0x06,0x56,0x27,0x32,0x32,0x36, // 30-3F
0x32,0xB2,0x1A,0x3B,0xBC,0x91,0xD4,0x7B,0x58,0xFC,0x0B,0x55,0x2A,0x15,0xBC,0x40, // 40-4F
0x92,0x0B,0x5B,0x7C,0x0A,0x95,0x12,0x35,0xB8,0x63,0xD2,0x0B,0x3B,0xF0,0xC7,0x14, // 50-5F
0x51,0x5C,0x94,0x86,0x94,0x59,0x5C,0xFC,0x1B,0x17,0x3A,0x3F,0x6B,0x37,0x32,0x32, // 60-6F
0x30,0x32,0x72,0x7A,0x13,0xB7,0x26,0x60,0x7A,0x13,0xB7,0x26,0x50,0xBA,0x13,0xB4, // 70-7F
0x2A,0x50,0xBA,0x13,0xB5,0x2E,0x40,0xFA,0x13,0x95,0xAE,0x40,0x38,0x18,0x9A,0x92, // 80-8F
0xB0,0x38,0x00,0xFA,0x12,0xB1,0x7E,0x00,0xDB,0x96,0xA1,0x7C,0x08,0xDB,0x9A,0x91, // 90-9F
0xBC,0x08,0xD8,0x1A,0x86,0xE2,0x70,0x39,0x1F,0x86,0xE0,0x78,0x7E,0x03,0xE7,0x64, // A0-AF
0x51,0x9C,0x8F,0x34,0x6F,0x4E,0x41,0xFC,0x0B,0xD5,0xAE,0x41,0xFC,0x0B,0xD5,0xAE, // B0-BF
0x41,0xFC,0x3B,0x70,0x71,0x64,0x33,0x32,0x12,0x32,0x32,0x36,0x70,0x34,0x2B,0x56, // C0-CF
0x22,0x70,0x3A,0x13,0xB7,0x26,0x60,0xBA,0x1B,0x94,0xAA,0x40,0x38,0x00,0xFA,0xB2, // D0-DF
0xE2,0xA2,0x67,0x32,0x32,0x12,0x32,0xB2,0x32,0x32,0x32,0x32,0x75,0xA3,0x26,0x7B, // E0-EF
0x83,0x26,0xF9,0x83,0x2E,0xFF,0xE3,0x16,0x7D,0xC0,0x1E,0x63,0x21,0x07,0xE3,0x01, // F0-FF
};
size_t bytes_read;
off_t encrypted_offset = data->header_size;
int i;
if (sead->is_sab) {
char descriptor[255], name[255];
bytes_read = streamfile->read(streamfile, dest, offset, length);
if (sead->descriptor_size > 255 || sead->sndname_size > 255) goto fail;
/* decrypt data (xor) */
if (offset >= encrypted_offset) {
for (i = 0; i < bytes_read; i++) {
dest[i] ^= encryption_key[(data->key_start + (offset - encrypted_offset) + i) % 0x100];
read_string(descriptor,sead->descriptor_size+1,sead->descriptor_offset, sf);
read_string(name,sead->sndname_size+1,sead->sndname_offset, sf);
snprintf(buf,buf_size, "%s/%s", descriptor, name);
}
else {
char descriptor[255], name[255], mode[255];
if (sead->descriptor_size > 255 || sead->filename_size > 255 || sead->cuename_size > 255 || sead->modename_size > 255) goto fail;
read_string(descriptor,sead->descriptor_size+1,sead->descriptor_offset, sf);
//read_string(filename,sead->filename_size+1,sead->filename_offset, sf); /* same as filename, not too interesting */
if (sead->cuename_offset)
read_string(name,sead->cuename_size+1,sead->cuename_offset, sf);
else if (sead->instname_offset)
read_string(name,sead->instname_size+1,sead->instname_offset, sf);
else
strcpy(name, "?");
read_string(mode,sead->modename_size+1,sead->modename_offset, sf);
/* default mode in most files, not very interesting */
if (strcmp(mode, "Mode") == 0 || strcmp(mode, "Mode0") == 0)
snprintf(buf,buf_size, "%s/%s", descriptor, name);
else
snprintf(buf,buf_size, "%s/%s/%s", descriptor, name, mode);
}
return;
fail:
VGM_LOG("SEAD: bad name found\n");
}
static void parse_sead_mab_name(sead_header *sead, STREAMFILE *sf) {
int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE;
int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE;
int i, entries, cue, mode, cue_count, mode_count;
off_t entry_offset, cue_offset, mode_offset, name_offset, table_offset;
size_t name_size;
//int wave, wave_count; off_t wave_offset, subtable_offset; uint16_t wave_id;
int name = 0;
/* find which name corresponds to our song (mabf can have N subsongs
* and X cues + Y modes and also Z instruments, one of which should reference it) */
//todo exact name matching unknown, assumes subsong N = name N
/* parse "musc" (music cue?) */
entries = read_16bit(sead->musc_offset + 0x04, sf);
for (i = 0; i < entries; i++) {
entry_offset = sead->musc_offset + read_32bit(sead->musc_offset + 0x10 + i*0x04, sf);
/* 0x00: config? */
sead->filename_offset = entry_offset + read_16bit(entry_offset + 0x02, sf);
cue_count = read_8bit(entry_offset + 0x04, sf);
mode_count = read_8bit(entry_offset + 0x05, sf);
/* 0x06: some low number? */
/* 0x07: always 0x80? (apparently not an offset/size) */
/* 0x08: id? */
/* 0x0a: 0? */
/* 0x44: sample rate */
/* others: unknown/null */
sead->filename_size = read_8bit(entry_offset + 0x48, sf);
/* table points to all cue offsets first then all modes offsets */
table_offset = align_size_to_block(sead->filename_offset + sead->filename_size + 0x01, 0x10);
/* cue name (ex. "bgm_007_take2" / "bgm_007s" / etc subsongs) */
for (cue = 0; cue < cue_count; cue++) {
cue_offset = sead->musc_offset + 0x20 + read_32bit(table_offset + cue*0x04, sf);
/* 0x00: id? */
name_offset = cue_offset + read_16bit(cue_offset + 0x02, sf);
name_size = read_8bit(cue_offset + 0x04, sf);
//wave_count = read_8bit(cue_offset + 0x05, sf);
/* 0x06: ? */
/* 0x0c: num samples */
/* 0x10: loop start */
/* 0x14: loop end */
/* 0x18: flag? */
/* others: ? */
name++;
if (name == sead->target_subsong || cue_count == 1) {
sead->cuename_offset = name_offset;
sead->cuename_size = name_size;
break;
}
#if 0 //this works for some games like KH3 but not others like FFXII
/* subtable: first N wave refs + ? unk refs (rarely more than 1 each) */
subtable_offset = align_size_to_block(name_offset + name_size + 1, 0x10);
for (wave = 0; wave < wave_count; wave++) {
wave_offset = cue_offset + read_32bit(subtable_offset + wave*0x04, sf);
/* 0x00: config? */
/* 0x02: entry size */
wave_id = read_16bit(wave_offset + 0x04, sf);
/* 0x06: null? */
/* 0x08: null? */
/* 0x0c: some id/config? */
if (wave_id == sead->wave_id) {
sead->cuename_offset = name_offset;
sead->cuename_size = name_size;
break;
}
}
if (sead->cuename_offset)
break;
#endif
}
/* mode name (ex. almost always "Mode" and only 1 entry, rarely "Water" / "Restaurant" / etc)
* no idea how modes are referenced (perhaps manually with in-game events)
* so just a quick hack, only found multiple in FFXV's bgm_gardina */
if (mode_count == sead->total_subsongs)
mode = sead->target_subsong - 1;
else
mode = 0;
{ //for (mode = 0; mode < mode_count; mode++) {
mode_offset = sead->musc_offset + 0x20 + read_32bit(table_offset + cue_count*0x04 + mode*0x04, sf);
/* 0x00: id? */
name_offset = mode_offset + read_16bit(mode_offset + 0x02, sf);
/* 0x04: mode id */
name_size = read_8bit(mode_offset + 0x06, sf);
/* 0x08: offset? */
/* others: floats and stuff */
sead->modename_offset = name_offset;
sead->modename_size = name_size;
}
}
return bytes_read;
/* parse "inst" (instruments) */
entries = read_16bit(sead->inst_offset + 0x04, sf);
for (i = 0; i < entries; i++) {
entry_offset = sead->inst_offset + read_32bit(sead->inst_offset + 0x10 + i*0x04, sf);
/* 0x00: id? */
/* 0x02: base size? */
/* 0x05: count? */
//wave_count = read_8bit(entry_offset + 0x06, sf);
/* 0x0c: num samples */
/* 0x10: loop start */
/* 0x14: loop end */
/* 0x18: flag? */
/* others: ? */
/* no apparent fields and inst is very rare (ex. KH3 tut) */
name_offset = entry_offset + 0x30;
name_size = 0x0F;
name++;
if (name == sead->target_subsong) {
sead->instname_offset = name_offset;
sead->instname_size = name_size;
break;
}
#if 0 //not actually tested
if (wave_count != 1) break; /* ? */
/* subtable: N wave refs? */
subtable_offset = align_size_to_block(name_offset + name_size + 1, 0x10);
for (wave = 0; wave < wave_count; wave++) {
wave_offset = subtable_offset + read_32bit(subtable_offset + wave*0x04, sf);
/* 0x00: config? */
/* 0x02: entry size? */
wave_id = read_16bit(wave_offset + 0x04, sf);
/* 0x06: ? */
/* 0x08: id/crc? */
/* 0x0c: ? */
/* 0x10: sample rate */
/* others: null? */
if (wave_id == sead->wave_id) {
sead->instname_offset = name_offset;
sead->instname_size = name_size;
break;
}
}
if (sead->instname_offset)
break;
#endif
}
}
static STREAMFILE* setup_sead_hca_streamfile(STREAMFILE *streamFile, off_t subfile_offset, size_t subfile_size, int encryption, size_t header_size, size_t key_start) {
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
static void parse_sead_sab_name(sead_header *sead, STREAMFILE *sf) {
int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE;
int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE;
int i, entries, snd_id, wave_id, snd_found = 0;
size_t size;
off_t entry_offset;
/* setup subfile */
new_streamFile = open_wrap_streamfile(streamFile);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
new_streamFile = open_clamp_streamfile(temp_streamFile, subfile_offset,subfile_size);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
//todo looks mostly correct for many subsongs but in rare cases wave_ids aren't referenced
// or maybe id needs another jump (seq?) (ex. DQB se_break_soil, FFXV aircraftzeroone)
if (encryption) {
sead_decryption_data io_data = {0};
size_t io_data_size = sizeof(sead_decryption_data);
/* parse "trk" (track info) */
entries = read_16bit(sead->trk_offset + 0x04, sf);
for (i = 0; i < entries; i++) {
entry_offset = sead->trk_offset + read_32bit(sead->trk_offset + 0x10 + i*0x04, sf);
io_data.header_size = header_size;
io_data.key_start = key_start;
/* 0x00: type/count? */
size = read_16bit(entry_offset + 0x02, sf); /* bigger if 'type=03' */
/* 0x04: trk id? */
/* 0x04: some id? */
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, sead_decryption_read,NULL);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
if (size > 0x10) {
snd_id = read_8bit(entry_offset + 0x10, sf);
wave_id = read_16bit(entry_offset + 0x11, sf);
}
else {
snd_id = read_16bit(entry_offset + 0x08, sf);
wave_id = read_16bit(entry_offset + 0x0a, sf);
}
if (wave_id == sead->wave_id) {
snd_found = 1;
break;
}
}
new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,"hca");
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
if (!snd_found) {
if (sead->total_subsongs == 1) {
snd_id = 0; /* meh */
VGM_LOG("SEAD: snd_id not found, using first\n");
} else {
return;
}
}
return temp_streamFile;
fail:
close_streamfile(temp_streamFile);
return NULL;
/* parse "snd " (sound info) */
{
off_t entry_offset = sead->snd_offset + read_32bit(sead->snd_offset + 0x10 + snd_id*0x04, sf);
/* 0x00: config? */
sead->sndname_offset = entry_offset + read_16bit(entry_offset + 0x02, sf);
/* 0x04: count of ? */
/* 0x05: count of ? (0 if no sound exist in file) */
/* 0x06: some low number? */
/* 0x07: always 0x80? (apparently not an offset/size) */
/* 0x08: snd id */
/* 0x0a: 0? */
/* 0x0c: 1.0? */
/* 0x1a: header size? */
/* 0x1c: 30.0? * */
/* 0x24: crc/id? */
/* 0x46: header size? */
/* 0x4c: header size? */
if (sead->version == 1) {
sead->sndname_offset -= 0x10;
sead->sndname_size = read_8bit(entry_offset + 0x08, sf);
}
else {
sead->sndname_size = read_8bit(entry_offset + 0x23, sf);
}
/* 0x24: unique id? (referenced in "seq" section?) */
/* others: probably sound config like pan/volume (has floats and stuff) */
}
}
static int parse_sead(sead_header *sead, STREAMFILE *sf) {
int32_t (*read_32bit)(off_t,STREAMFILE*) = sead->big_endian ? read_32bitBE : read_32bitLE;
int16_t (*read_16bit)(off_t,STREAMFILE*) = sead->big_endian ? read_16bitBE : read_16bitLE;
/** base header **/
sead->version = read_8bit(0x04, sf); /* usually 0x02, rarely 0x01 (ex FF XV early songs) */
/* 0x05(1): 0/1? */
/* 0x06(2): ? (usually 0x10, rarely 0x20) */
/* 0x08(1): 3/4? */
sead->descriptor_size = read_8bit(0x09, sf);
/* 0x0a(2): ? */
if (read_32bit(0x0c, sf) != get_streamfile_size(sf))
goto fail;
if (sead->descriptor_size == 0) /* not set when version == 1 */
sead->descriptor_size = 0x0f;
sead->descriptor_offset = 0x10; /* file descriptor ("BGM", "Music2", "SE", etc, long names are ok) */
sead->sections_offset = sead->descriptor_offset + (sead->descriptor_size + 0x01); /* string null matters for padding */
sead->sections_offset = align_size_to_block(sead->sections_offset, 0x10);
/** offsets to sections **/
if (sead->is_sab) {
if (read_32bitBE(sead->sections_offset + 0x00, sf) != 0x736E6420) goto fail; /* "snd " (sonds) */
if (read_32bitBE(sead->sections_offset + 0x10, sf) != 0x73657120) goto fail; /* "seq " (unknown) */
if (read_32bitBE(sead->sections_offset + 0x20, sf) != 0x74726B20) goto fail; /* "trk " (unknown) */
if (read_32bitBE(sead->sections_offset + 0x30, sf) != 0x6D74726C) goto fail; /* "mtrl" (headers/streams) */
sead->snd_offset = read_32bit(sead->sections_offset + 0x08, sf);
//sead->seq_offset = read_32bit(sead->sections_offset + 0x18, sf);
sead->trk_offset = read_32bit(sead->sections_offset + 0x28, sf);
sead->mtrl_offset = read_32bit(sead->sections_offset + 0x38, sf);
}
else if (sead->is_mab) {
if (read_32bitBE(sead->sections_offset + 0x00, sf) != 0x6D757363) goto fail; /* "musc" (cues) */
if (read_32bitBE(sead->sections_offset + 0x10, sf) != 0x696E7374) goto fail; /* "inst" (instruments) */
if (read_32bitBE(sead->sections_offset + 0x20, sf) != 0x6D74726C) goto fail; /* "mtrl" (headers/streams) */
sead->musc_offset = read_32bit(sead->sections_offset + 0x08, sf);
sead->inst_offset = read_32bit(sead->sections_offset + 0x18, sf);
sead->mtrl_offset = read_32bit(sead->sections_offset + 0x28, sf);
}
else {
goto fail;
}
/* section format at offset:
* 0x00(2): 0/1?
* 0x02(2): header size? (always 0x10)
* 0x04(2): entries
* 0x06(+): padded to 0x10
* 0x10 + 0x04*entry: offset to entry from table start (also padded to 0x10 at the end) */
/* find meta_offset in "mtrl" and total subsongs */
{
int i, entries;
entries = read_16bit(sead->mtrl_offset+0x04, sf);
if (sead->target_subsong == 0) sead->target_subsong = 1;
sead->total_subsongs = 0;
sead->meta_offset = 0;
/* manually find subsongs as entries can be dummy (ex. sfx banks in Dissidia Opera Omnia) */
for (i = 0; i < entries; i++) {
off_t entry_offset = sead->mtrl_offset + read_32bit(sead->mtrl_offset + 0x10 + i*0x04, sf);
if (read_8bit(entry_offset + 0x05, sf) == 0) {
continue; /* codec 0 when dummy (see stream header) */
}
sead->total_subsongs++;
if (!sead->meta_offset && sead->total_subsongs == sead->target_subsong) {
sead->meta_offset = entry_offset;
}
}
if (sead->meta_offset == 0) goto fail;
/* SAB can contain 0 entries too */
}
/** stream header **/
/* 0x00(2): 0x00/01? */
/* 0x02(2): base entry size? (0x20) */
sead->channel_count = read_8bit(sead->meta_offset + 0x04, sf);
sead->codec = read_8bit(sead->meta_offset + 0x05, sf);
sead->wave_id = read_16bit(sead->meta_offset + 0x06, sf); /* 0..N */
sead->sample_rate = read_32bit(sead->meta_offset + 0x08, sf);
sead->loop_start = read_32bit(sead->meta_offset + 0x0c, sf); /* in samples but usually ignored */
sead->loop_end = read_32bit(sead->meta_offset + 0x10, sf);
sead->extradata_size = read_32bit(sead->meta_offset + 0x14, sf); /* including subfile header, can be 0 */
sead->stream_size = read_32bit(sead->meta_offset + 0x18, sf); /* not including subfile header */
sead->special_size = read_32bit(sead->meta_offset + 0x1c, sf);
sead->loop_flag = (sead->loop_end > 0);
sead->extradata_offset = sead->meta_offset + 0x20;
/** info section (get stream name) **/
if (sead->is_sab) {
parse_sead_sab_name(sead, sf);
}
else if (sead->is_mab) {
parse_sead_mab_name(sead, sf);
}
build_readable_name(sead->readable_name, sizeof(sead->readable_name), sead, sf);
return 1;
fail:
return 0;
}

View File

@ -0,0 +1,86 @@
#ifndef _SQEX_SEAD_STREAMFILE_H_
#define _SQEX_SEAD_STREAMFILE_H_
#include "../streamfile.h"
static STREAMFILE* setup_sqex_sead_streamfile(STREAMFILE *streamFile, off_t subfile_offset, size_t subfile_size, int encryption, size_t header_size, size_t key_start);
typedef struct {
size_t header_size;
size_t key_start;
} sqex_sead_decryption_data;
static size_t sqex_sead_decryption_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, sqex_sead_decryption_data* data) {
/* Found in FFXII_TZA.exe (same key in SCD Ogg V3) */
static const uint8_t encryption_key[0x100] = {
0x3A,0x32,0x32,0x32,0x03,0x7E,0x12,0xF7,0xB2,0xE2,0xA2,0x67,0x32,0x32,0x22,0x32, // 00-0F
0x32,0x52,0x16,0x1B,0x3C,0xA1,0x54,0x7B,0x1B,0x97,0xA6,0x93,0x1A,0x4B,0xAA,0xA6, // 10-1F
0x7A,0x7B,0x1B,0x97,0xA6,0xF7,0x02,0xBB,0xAA,0xA6,0xBB,0xF7,0x2A,0x51,0xBE,0x03, // 20-2F
0xF4,0x2A,0x51,0xBE,0x03,0xF4,0x2A,0x51,0xBE,0x12,0x06,0x56,0x27,0x32,0x32,0x36, // 30-3F
0x32,0xB2,0x1A,0x3B,0xBC,0x91,0xD4,0x7B,0x58,0xFC,0x0B,0x55,0x2A,0x15,0xBC,0x40, // 40-4F
0x92,0x0B,0x5B,0x7C,0x0A,0x95,0x12,0x35,0xB8,0x63,0xD2,0x0B,0x3B,0xF0,0xC7,0x14, // 50-5F
0x51,0x5C,0x94,0x86,0x94,0x59,0x5C,0xFC,0x1B,0x17,0x3A,0x3F,0x6B,0x37,0x32,0x32, // 60-6F
0x30,0x32,0x72,0x7A,0x13,0xB7,0x26,0x60,0x7A,0x13,0xB7,0x26,0x50,0xBA,0x13,0xB4, // 70-7F
0x2A,0x50,0xBA,0x13,0xB5,0x2E,0x40,0xFA,0x13,0x95,0xAE,0x40,0x38,0x18,0x9A,0x92, // 80-8F
0xB0,0x38,0x00,0xFA,0x12,0xB1,0x7E,0x00,0xDB,0x96,0xA1,0x7C,0x08,0xDB,0x9A,0x91, // 90-9F
0xBC,0x08,0xD8,0x1A,0x86,0xE2,0x70,0x39,0x1F,0x86,0xE0,0x78,0x7E,0x03,0xE7,0x64, // A0-AF
0x51,0x9C,0x8F,0x34,0x6F,0x4E,0x41,0xFC,0x0B,0xD5,0xAE,0x41,0xFC,0x0B,0xD5,0xAE, // B0-BF
0x41,0xFC,0x3B,0x70,0x71,0x64,0x33,0x32,0x12,0x32,0x32,0x36,0x70,0x34,0x2B,0x56, // C0-CF
0x22,0x70,0x3A,0x13,0xB7,0x26,0x60,0xBA,0x1B,0x94,0xAA,0x40,0x38,0x00,0xFA,0xB2, // D0-DF
0xE2,0xA2,0x67,0x32,0x32,0x12,0x32,0xB2,0x32,0x32,0x32,0x32,0x75,0xA3,0x26,0x7B, // E0-EF
0x83,0x26,0xF9,0x83,0x2E,0xFF,0xE3,0x16,0x7D,0xC0,0x1E,0x63,0x21,0x07,0xE3,0x01, // F0-FF
};
size_t bytes_read;
off_t encrypted_offset = data->header_size;
int i;
bytes_read = streamfile->read(streamfile, dest, offset, length);
/* decrypt data (xor) */
if (offset >= encrypted_offset) {
for (i = 0; i < bytes_read; i++) {
dest[i] ^= encryption_key[(data->key_start + (offset - encrypted_offset) + i) % 0x100];
}
}
return bytes_read;
}
/* decrypts subfile if neccessary */
static STREAMFILE* setup_sqex_sead_streamfile(STREAMFILE *streamFile, off_t subfile_offset, size_t subfile_size, int encryption, size_t header_size, size_t key_start) {
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
/* setup subfile */
new_streamFile = open_wrap_streamfile(streamFile);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
new_streamFile = open_clamp_streamfile(temp_streamFile, subfile_offset,subfile_size);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
if (encryption) {
sqex_sead_decryption_data io_data = {0};
size_t io_data_size = sizeof(sqex_sead_decryption_data);
io_data.header_size = header_size;
io_data.key_start = key_start;
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, sqex_sead_decryption_read,NULL);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
}
new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,"hca");
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
return temp_streamFile;
fail:
close_streamfile(temp_streamFile);
return NULL;
}
#endif /* _SQEX_SEAD_STREAMFILE_H_ */

View File

@ -81,6 +81,11 @@ typedef struct {
int coef_table_set;
uint8_t coef_table[0x02*16 * 16]; /* reasonable max */
int hist_set;
uint32_t hist_offset;
uint32_t hist_spacing;
uint32_t hist_big_endian;
int num_samples_data_size;
int target_subsong;
@ -378,29 +383,41 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
}
/* get coefs */
for (i = 0; i < vgmstream->channels; i++) {
{
int16_t (*read_16bit)(off_t , STREAMFILE*) = txth.coef_big_endian ? read_16bitBE : read_16bitLE;
int16_t (*get_16bit)(uint8_t * p) = txth.coef_big_endian ? get_16bitBE : get_16bitLE;
/* normal/split coefs */
if (txth.coef_mode == 0) { /* normal mode */
for (j = 0; j < 16; j++) {
int16_t coef;
if (txth.coef_table_set)
coef = get_16bit(txth.coef_table + i*txth.coef_spacing + j*2);
else
coef = read_16bit(txth.coef_offset + i*txth.coef_spacing + j*2, txth.streamHead);
vgmstream->ch[i].adpcm_coef[j] = coef;
for (i = 0; i < vgmstream->channels; i++) {
if (txth.coef_mode == 0) { /* normal coefs */
for (j = 0; j < 16; j++) {
int16_t coef;
if (txth.coef_table_set)
coef = get_16bit(txth.coef_table + i*txth.coef_spacing + j*2);
else
coef = read_16bit(txth.coef_offset + i*txth.coef_spacing + j*2, txth.streamHead);
vgmstream->ch[i].adpcm_coef[j] = coef;
}
}
else { /* split coefs */
goto fail; //IDK what is this
/*
for (j = 0; j < 8; j++) {
vgmstream->ch[i].adpcm_coef[j*2] = read_16bit(genh.coef_offset + i*genh.coef_spacing + j*2, txth.streamHead);
vgmstream->ch[i].adpcm_coef[j*2+1] = read_16bit(genh.coef_split_offset + i*genh.coef_split_spacing + j*2, txth.streamHead);
}
*/
}
}
else { /* split coefs */
goto fail; //IDK what is this
/*
for (j = 0; j < 8; j++) {
vgmstream->ch[i].adpcm_coef[j*2] = read_16bit(genh.coef_offset + i*genh.coef_spacing + j*2, txth.streamHead);
vgmstream->ch[i].adpcm_coef[j*2+1] = read_16bit(genh.coef_split_offset + i*genh.coef_split_spacing + j*2, txth.streamHead);
}
*/
}
/* get hist */
if (txth.hist_set) {
int16_t (*read_16bit)(off_t , STREAMFILE*) = txth.hist_big_endian ? read_16bitBE : read_16bitLE;
for (i = 0; i < vgmstream->channels; i++) {
off_t offset = txth.hist_offset + i*txth.hist_spacing;
vgmstream->ch[i].adpcm_history1_16 = read_16bit(offset + 0x00, txth.streamHead);
vgmstream->ch[i].adpcm_history2_16 = read_16bit(offset + 0x02, txth.streamHead);
}
}
@ -1051,6 +1068,22 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char
txth->coef_table_set = 1;
}
/* HIST */
else if (is_string(key,"hist_offset")) {
if (!parse_num(txth->streamHead,txth,val, &txth->hist_offset)) goto fail;
txth->hist_set = 1;
}
else if (is_string(key,"hist_spacing")) {
if (!parse_num(txth->streamHead,txth,val, &txth->hist_spacing)) goto fail;
}
else if (is_string(key,"hist_endianness")) {
if (is_string(val, "BE"))
txth->hist_big_endian = 1;
else if (is_string(val, "LE"))
txth->hist_big_endian = 0;
else if (!parse_num(txth->streamHead,txth,val, &txth->hist_big_endian)) goto fail;
}
/* SUBSONGS */
else if (is_string(key,"subsong_count")) {
if (!parse_num(txth->streamHead,txth,val, &txth->subsong_count)) goto fail;

View File

@ -5,10 +5,11 @@
#define TXTP_LINE_MAX 1024
#define TXTP_MIXING_MAX 128
#define TXTP_MIXING_MAX 512
#define TXTP_GROUP_MODE_SEGMENTED 'S'
#define TXTP_GROUP_MODE_LAYERED 'L'
#define TXTP_GROUP_REPEAT 'R'
#define TXTP_POSITION_LOOPS 'L'
/* mixing info */
typedef enum {
@ -50,6 +51,8 @@ typedef struct {
double time_start;
double time_end;
double time_post;
double position;
char position_type;
/* macros */
int max;
@ -69,8 +72,11 @@ typedef struct {
int mixing_count;
txtp_mix_data mixing[TXTP_MIXING_MAX];
int config_loop_count_set;
double config_loop_count;
int config_fade_time_set;
double config_fade_time;
int config_fade_delay_set;
double config_fade_delay;
int config_ignore_loop;
int config_force_loop;
@ -420,16 +426,21 @@ fail:
static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) {
vgmstream->config_loop_count = current->config_loop_count;
vgmstream->config_fade_time = current->config_fade_time;
vgmstream->config_fade_delay = current->config_fade_delay;
vgmstream->config_ignore_loop = current->config_ignore_loop;
vgmstream->config_force_loop = current->config_force_loop;
vgmstream->config_ignore_fade = current->config_ignore_fade;
if (current->config_loop_count_set)
vgmstream->config_loop_count = current->config_loop_count;
if (current->config_fade_time_set)
vgmstream->config_fade_time = current->config_fade_time;
if (current->config_fade_delay_set)
vgmstream->config_fade_delay = current->config_fade_delay;
if (current->config_ignore_loop)
vgmstream->config_ignore_loop = current->config_ignore_loop;
if (current->config_force_loop)
vgmstream->config_force_loop = current->config_force_loop;
if (current->config_ignore_fade)
vgmstream->config_ignore_fade = current->config_ignore_fade;
if (current->sample_rate > 0) {
if (current->sample_rate > 0)
vgmstream->sample_rate = current->sample_rate;
}
if (current->loop_install) {
if (current->loop_start_second > 0 || current->loop_end_second > 0) {
@ -462,44 +473,57 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) {
/* copy mixing list (should be done last as some mixes depend on config) */
if (current->mixing_count > 0) {
int m;
int m, position_samples;
for (m = 0; m < current->mixing_count; m++) {
txtp_mix_data mix = current->mixing[m];
txtp_mix_data *mix = &current->mixing[m];
switch(mix.command) {
switch(mix->command) {
/* base mixes */
case MIX_SWAP: mixing_push_swap(vgmstream, mix.ch_dst, mix.ch_src); break;
case MIX_ADD: mixing_push_add(vgmstream, mix.ch_dst, mix.ch_src, 1.0); break;
case MIX_ADD_VOLUME: mixing_push_add(vgmstream, mix.ch_dst, mix.ch_src, mix.vol); break;
case MIX_VOLUME: mixing_push_volume(vgmstream, mix.ch_dst, mix.vol); break;
case MIX_LIMIT: mixing_push_limit(vgmstream, mix.ch_dst, mix.vol); break;
case MIX_UPMIX: mixing_push_upmix(vgmstream, mix.ch_dst); break;
case MIX_DOWNMIX: mixing_push_downmix(vgmstream, mix.ch_dst); break;
case MIX_KILLMIX: mixing_push_killmix(vgmstream, mix.ch_dst); break;
case MIX_SWAP: mixing_push_swap(vgmstream, mix->ch_dst, mix->ch_src); break;
case MIX_ADD: mixing_push_add(vgmstream, mix->ch_dst, mix->ch_src, 1.0); break;
case MIX_ADD_VOLUME: mixing_push_add(vgmstream, mix->ch_dst, mix->ch_src, mix->vol); break;
case MIX_VOLUME: mixing_push_volume(vgmstream, mix->ch_dst, mix->vol); break;
case MIX_LIMIT: mixing_push_limit(vgmstream, mix->ch_dst, mix->vol); break;
case MIX_UPMIX: mixing_push_upmix(vgmstream, mix->ch_dst); break;
case MIX_DOWNMIX: mixing_push_downmix(vgmstream, mix->ch_dst); break;
case MIX_KILLMIX: mixing_push_killmix(vgmstream, mix->ch_dst); break;
case MIX_FADE:
/* Convert from time to samples now that sample rate is final.
* Samples and time values may be mixed though, so it's done for every
* value (if one is 0 the other will be too, though) */
if (mix.time_pre > 0.0) mix.sample_pre = mix.time_pre * vgmstream->sample_rate;
if (mix.time_start > 0.0) mix.sample_start = mix.time_start * vgmstream->sample_rate;
if (mix.time_end > 0.0) mix.sample_end = mix.time_end * vgmstream->sample_rate;
if (mix.time_post > 0.0) mix.sample_post = mix.time_post * vgmstream->sample_rate;
if (mix->time_pre > 0.0) mix->sample_pre = mix->time_pre * vgmstream->sample_rate;
if (mix->time_start > 0.0) mix->sample_start = mix->time_start * vgmstream->sample_rate;
if (mix->time_end > 0.0) mix->sample_end = mix->time_end * vgmstream->sample_rate;
if (mix->time_post > 0.0) mix->sample_post = mix->time_post * vgmstream->sample_rate;
/* convert special meaning too */
if (mix.time_pre < 0.0) mix.sample_pre = -1;
if (mix.time_post < 0.0) mix.sample_post = -1;
if (mix->time_pre < 0.0) mix->sample_pre = -1;
if (mix->time_post < 0.0) mix->sample_post = -1;
mixing_push_fade(vgmstream, mix.ch_dst, mix.vol_start, mix.vol_end, mix.shape,
mix.sample_pre, mix.sample_start, mix.sample_end, mix.sample_post);
if (mix->position_type == TXTP_POSITION_LOOPS && vgmstream->loop_flag) {
int loop_pre = vgmstream->loop_start_sample;
int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
position_samples = loop_pre + loop_samples * mix->position;
if (mix->sample_pre >= 0) mix->sample_pre += position_samples;
mix->sample_start += position_samples;
mix->sample_end += position_samples;
if (mix->sample_post >= 0) mix->sample_post += position_samples;
}
mixing_push_fade(vgmstream, mix->ch_dst, mix->vol_start, mix->vol_end, mix->shape,
mix->sample_pre, mix->sample_start, mix->sample_end, mix->sample_post);
break;
/* macro mixes */
case MACRO_VOLUME: mixing_macro_volume(vgmstream, mix.vol, mix.mask); break;
case MACRO_TRACK: mixing_macro_track(vgmstream, mix.mask); break;
case MACRO_LAYER: mixing_macro_layer(vgmstream, mix.max, mix.mask, mix.mode); break;
case MACRO_CROSSTRACK: mixing_macro_crosstrack(vgmstream, mix.max); break;
case MACRO_CROSSLAYER: mixing_macro_crosslayer(vgmstream, mix.max, mix.mode); break;
case MACRO_DOWNMIX: mixing_macro_downmix(vgmstream, mix.max); break;
case MACRO_VOLUME: mixing_macro_volume(vgmstream, mix->vol, mix->mask); break;
case MACRO_TRACK: mixing_macro_track(vgmstream, mix->mask); break;
case MACRO_LAYER: mixing_macro_layer(vgmstream, mix->max, mix->mask, mix->mode); break;
case MACRO_CROSSTRACK: mixing_macro_crosstrack(vgmstream, mix->max); break;
case MACRO_CROSSLAYER: mixing_macro_crosslayer(vgmstream, mix->max, mix->mode); break;
case MACRO_DOWNMIX: mixing_macro_downmix(vgmstream, mix->max); break;
default:
break;
@ -545,14 +569,17 @@ static void clean_filename(char * filename) {
* - %n: special match (not counted in return value), chars consumed until that point (can appear and be set multiple times)
*/
static int get_double(const char * config, double *value) {
static int get_double(const char * config, double *value, int *is_set) {
int n, m;
double temp;
if (is_set) *is_set = 0;
m = sscanf(config, " %lf%n", &temp,&n);
if (m != 1 || temp < 0)
return 0;
if (is_set) *is_set = 1;
*value = temp;
return n;
}
@ -569,6 +596,25 @@ static int get_int(const char * config, int *value) {
return n;
}
static int get_position(const char * config, double *value_f, char *value_type) {
int n,m;
double temp_f;
char temp_c;
/* test if format is position: N.n(type) */
m = sscanf(config, " %lf%c%n", &temp_f,&temp_c,&n);
if (m != 2 || temp_f < 0.0)
return 0;
/* test accepted chars as it will capture anything */
if (temp_c != TXTP_POSITION_LOOPS)
return 0;
*value_f = temp_f;
*value_type = temp_c;
return n;
}
static int get_time(const char * config, double *value_f, int32_t *value_i) {
int n,m;
int temp_i1, temp_i2;
@ -752,6 +798,11 @@ static int get_fade(const char * config, txtp_mix_data *mix, int *out_n) {
mix->time_pre = -1.0;
mix->sample_pre = -1;
n = get_position(config, &mix->position, &mix->position_type);
//if (n == 0) goto fail; /* optional */
config += n;
tn += n;
n = get_time(config, &mix->time_start, &mix->sample_start);
if (n == 0) goto fail;
config += n;
@ -785,8 +836,8 @@ void add_mixing(txtp_entry* cfg, txtp_mix_data* mix, txtp_mix_t command) {
return;
}
/* parsers reads ch1 = first, but for mixing code ch0 = first
* (if parser reads ch0 here it'll become -1 with special meaning in code) */
/* parser reads ch1 = first, but for mixing code ch0 = first
* (if parser reads ch0 here it'll become -1 with meaning of "all channels" in mixing code) */
mix->ch_dst--;
mix->ch_src--;
mix->command = command;
@ -797,14 +848,19 @@ void add_mixing(txtp_entry* cfg, txtp_mix_data* mix, txtp_mix_t command) {
static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filename) {
/* don't memcopy to allow list additions and ignore values not set,
* as current can be "default" config */
//*current = *cfg;
if (filename)
strcpy(current->filename, filename);
current->subsong = cfg->subsong;
if (cfg->subsong)
current->subsong = cfg->subsong;
current->channel_mask = cfg->channel_mask;
//*current = *cfg; /* don't memcopy to allow list additions */ //todo save list first then memcpy
if (cfg->channel_mask)
current->channel_mask = cfg->channel_mask;
if (cfg->mixing_count > 0) {
int i;
@ -814,20 +870,40 @@ static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filenam
}
}
current->config_loop_count = cfg->config_loop_count;
current->config_fade_time = cfg->config_fade_time;
current->config_fade_delay = cfg->config_fade_delay;
current->config_ignore_loop = cfg->config_ignore_loop;
current->config_force_loop = cfg->config_force_loop;
current->config_ignore_fade = cfg->config_ignore_fade;
if (cfg->config_loop_count_set) {
current->config_loop_count_set = cfg->config_loop_count_set;
current->config_loop_count = cfg->config_loop_count;
}
if (cfg->config_fade_time_set) {
current->config_fade_time_set = cfg->config_fade_time_set;
current->config_fade_time = cfg->config_fade_time;
}
if (cfg->config_fade_delay_set) {
current->config_fade_delay_set = cfg->config_fade_delay_set;
current->config_fade_delay = cfg->config_fade_delay;
}
if (cfg->config_ignore_loop) {
current->config_ignore_loop = cfg->config_ignore_loop;
}
if (cfg->config_force_loop) {
current->config_force_loop = cfg->config_force_loop;
}
if (cfg->config_ignore_fade) {
current->config_ignore_fade = cfg->config_ignore_fade;
}
current->sample_rate = cfg->sample_rate;
current->loop_install = cfg->loop_install;
current->loop_end_max = cfg->loop_end_max;
current->loop_start_sample = cfg->loop_start_sample;
current->loop_start_second = cfg->loop_start_second;
current->loop_end_sample = cfg->loop_end_sample;
current->loop_end_second = cfg->loop_end_second;
if (cfg->sample_rate > 0) {
current->sample_rate = cfg->sample_rate;
}
if (cfg->loop_install) {
current->loop_install = cfg->loop_install;
current->loop_end_max = cfg->loop_end_max;
current->loop_start_sample = cfg->loop_start_sample;
current->loop_start_second = cfg->loop_start_second;
current->loop_end_sample = cfg->loop_end_sample;
current->loop_end_second = cfg->loop_end_second;
}
}
static void parse_config(txtp_entry *cfg, char *config) {
@ -982,15 +1058,15 @@ static void parse_config(txtp_entry *cfg, char *config) {
//;VGM_LOG("TXTP: ignore_fade=%i\n", cfg->config_ignore_fade);
}
else if (strcmp(command,"l") == 0) {
config += get_double(config, &cfg->config_loop_count);
config += get_double(config, &cfg->config_loop_count, &cfg->config_loop_count_set);
//;VGM_LOG("TXTP: loop_count=%f\n", cfg->config_loop_count);
}
else if (strcmp(command,"f") == 0) {
config += get_double(config, &cfg->config_fade_time);
config += get_double(config, &cfg->config_fade_time, &cfg->config_fade_time_set);
//;VGM_LOG("TXTP: fade_time=%f\n", cfg->config_fade_time);
}
else if (strcmp(command,"d") == 0) {
config += get_double(config, &cfg->config_fade_delay);
config += get_double(config, &cfg->config_fade_delay, &cfg->config_fade_delay_set);
//;VGM_LOG("TXTP: fade_delay %f\n", cfg->config_fade_delay);
}
else if (strcmp(command,"h") == 0) {
@ -1018,7 +1094,7 @@ static void parse_config(txtp_entry *cfg, char *config) {
else if (strcmp(command,"@volume") == 0) {
txtp_mix_data mix = {0};
nm = get_double(config, &mix.vol);
nm = get_double(config, &mix.vol, NULL);
config += nm;
if (nm == 0) continue;

View File

@ -47,7 +47,7 @@ VGMSTREAM * init_vgmstream_ubi_hx(STREAMFILE *streamFile) {
/* checks */
/* .hxd: Rayman Arena (all)
/* .hxd: Rayman Arena (all), PK: Out of Shadows (all)
* .hxc: Rayman 3 (PC), XIII (PC)
* .hx2: Rayman 3 (PS2), XIII (PS2)
* .hxg: Rayman 3 (GC), XIII (GC)
@ -299,7 +299,7 @@ static int parse_header(ubi_hx_header * hx, STREAMFILE *sf, off_t offset, size_t
offset += 0x08;
if (read_32bit(offset + 0x00, sf) != 0x01) goto fail;
/* 0x04: 0? */
/* 0x04: some kind of parent id shared by multiple Waves, or 0 */
offset += 0x08;
hx->stream_mode = read_8bit(offset, sf);
@ -352,9 +352,11 @@ static int parse_header(ubi_hx_header * hx, STREAMFILE *sf, off_t offset, size_t
}
}
switch(hx->stream_mode) {
case 0x01: /* static (smaller external file) */
case 0x03: /* stream (bigger external file) */
case 0x07: /* stream? */
resource_size = read_32bit(offset + 0x00, sf);
if (resource_size > sizeof(hx->resource_name)+1) goto fail;
read_string(hx->resource_name,resource_size+1, offset + 0x04, sf);

View File

@ -112,7 +112,7 @@ VGMSTREAM * init_vgmstream_xnb(STREAMFILE *streamFile) {
if (!block_align)
block_align = (bps == 8 ? 0x01 : 0x02) * channel_count;
vgmstream->coding_type = bps == 8 ? coding_PCM8_U_int : coding_PCM16LE;
vgmstream->coding_type = bps == 8 ? coding_PCM8_U : coding_PCM16LE;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = block_align / channel_count;
vgmstream->num_samples = pcm_bytes_to_samples(data_size, channel_count, bps);

View File

@ -48,7 +48,8 @@
* (and maybe improve memory cache?), though maybe it should call one function per operation.
*/
#define VGMSTREAM_MAX_MIXING 128
#define VGMSTREAM_MAX_MIXING 512
#define MIXING_PI 3.14159265358979323846f
/* mixing info */
@ -98,15 +99,15 @@ static int is_active(mixing_data *data, int32_t current_start, int32_t current_e
int32_t fade_start, fade_end;
for (i = 0; i < data->mixing_count; i++) {
mix_command_data mix = data->mixing_chain[i];
mix_command_data *mix = &data->mixing_chain[i];
if (mix.command != MIX_FADE)
if (mix->command != MIX_FADE)
return 1; /* has non-fades = active */
/* check is current range falls within a fade
* (assuming fades were already optimized on add) */
fade_start = mix.time_pre < 0 ? 0 : mix.time_pre;
fade_end = mix.time_post < 0 ? INT_MAX : mix.time_post;
fade_start = mix->time_pre < 0 ? 0 : mix->time_pre;
fade_end = mix->time_post < 0 ? INT_MAX : mix->time_post;
if (current_start < fade_end && current_end > fade_start)
return 1;
@ -115,24 +116,72 @@ static int is_active(mixing_data *data, int32_t current_start, int32_t current_e
return 0;
}
static int32_t get_current_pos(VGMSTREAM* vgmstream) {
static int32_t get_current_pos(VGMSTREAM* vgmstream, int32_t sample_count) {
int32_t current_pos;
if (vgmstream->loop_flag && vgmstream->current_sample > vgmstream->loop_start_sample) {
int loop_pre = vgmstream->loop_start_sample;
int loop_into = vgmstream->current_sample - vgmstream->loop_start_sample;
int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample;
current_pos = loop_pre + loop_into + loop_samples*vgmstream->loop_count;
if (vgmstream->loop_flag && vgmstream->loop_count > 0) {
int loop_pre = vgmstream->loop_start_sample; /* samples before looping */
int loop_into = (vgmstream->current_sample - vgmstream->loop_start_sample); /* samples after loop */
int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); /* looped section */
current_pos = loop_pre + (loop_samples * vgmstream->loop_count) + loop_into - sample_count;
}
else {
current_pos = vgmstream->current_sample;
current_pos = (vgmstream->current_sample - sample_count);
}
return current_pos;
}
static float get_fade_gain_curve(char shape, float index) {
float gain;
/* don't bother doing calcs near 0.0/1.0 */
if (index <= 0.0001f || index >= 0.9999f) {
return index;
}
//todo optimizations: interleave calcs, maybe use cosf, powf, etc? (with extra defines)
/* (curve math mostly from SoX/FFmpeg) */
switch(shape) {
/* 2.5f in L/E 'pow' is the attenuation factor, where 5.0 (100db) is common but a bit fast
* (alt calculations with 'exp' from FFmpeg use (factor)*ln(0.1) = -NN.N... */
case 'E': /* exponential (for fade-outs, closer to natural decay of sound) */
//gain = pow(0.1f, (1.0f - index) * 2.5f);
gain = exp(-5.75646273248511f * (1.0f - index));
break;
case 'L': /* logarithmic (inverse of the above, maybe for crossfades) */
//gain = 1 - pow(0.1f, (index) * 2.5f);
gain = 1 - exp(-5.75646273248511f * (index));
break;
case 'H': /* raised sine wave or cosine wave (for more musical crossfades) */
gain = (1.0f - cos(index * MIXING_PI)) / 2.0f;
break;
case 'Q': /* quarter of sine wave (for musical fades) */
gain = sin(index * MIXING_PI / 2.0f);
break;
case 'p': /* parabola (maybe for crossfades) */
gain = 1.0f - sqrt(1.0f - index);
break;
case 'P': /* inverted parabola (maybe for fades) */
gain = (1.0f - (1.0f - index) * (1.0f - index));
break;
case 'T': /* triangular/linear (simpler/sharper fades) */
default:
gain = index;
break;
}
return gain;
}
static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) {
//todo optimizations: interleave calcs, maybe use cosf, powf, etc?
float cur_vol = 0.0f;
if ((current_subpos >= mix->time_pre || mix->time_pre < 0) && current_subpos < mix->time_start) {
@ -173,41 +222,7 @@ static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t curr
* curves are complementary (exponential fade-in ~= logarithmic fade-out); the following
* are described taking fade-in = normal.
*/
/* (curve math mostly from SoX/FFmpeg) */
switch(mix->shape) {
/* 2.5f in L/E 'pow' is the attenuation factor, where 5.0 (100db) is common but a bit fast
* (alt calculations with 'exp' from FFmpeg use (factor)*ln(0.1) = -NN.N... */
case 'E': /* exponential (for fade-outs, closer to natural decay of sound) */
//gain = pow(0.1f, (1.0f - index) * 2.5f);
gain = exp(-5.75646273248511f * (1.0f - index));
break;
case 'L': /* logarithmic (inverse of the above, maybe for crossfades) */
//gain = 1 - pow(0.1f, (index) * 2.5f);
gain = 1 - exp(-5.75646273248511f * (index));
break;
case 'H': /* raised sine wave or cosine wave (for more musical crossfades) */
gain = (1.0f - cos(index * M_PI )) / 2.0f;
break;
case 'Q': /* quarter of sine wave (for musical fades) */
gain = sin(index * M_PI / 2.0f);
break;
case 'p': /* parabola (maybe for crossfades) */
gain = 1.0f - sqrt(1.0f - index);
break;
case 'P': /* inverted parabola (maybe for fades) */
gain = (1.0f - (1.0f - index) * (1.0f - index));
break;
case 'T': /* triangular/linear (simpler/sharper fades) */
default:
gain = index;
break;
}
gain = get_fade_gain_curve(mix->shape, index);
if (mix->vol_start < mix->vol_end) { /* fade in */
cur_vol = mix->vol_start + range_vol * gain;
@ -216,6 +231,7 @@ static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t curr
}
}
else {
/* fade is outside reach */
goto fail;
}
@ -230,20 +246,19 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
int ch, s, m, ok;
int32_t current_pos, current_subpos;
float temp_f, temp_min, temp_max, cur_vol;
float temp_f, temp_min, temp_max, cur_vol = 0.0f;
float *temp_mixbuf;
sample_t *temp_outbuf;
const float limiter_max = 32767.0f;
const float limiter_min = -32768.0f;
/* no support or not need to apply */
if (!data || !data->mixing_on || data->mixing_count == 0)
return;
/* try to skip if no ops apply (for example if fade set but does nothing yet) */
current_pos = get_current_pos(vgmstream);
current_pos = get_current_pos(vgmstream, sample_count);
if (!is_active(data, current_pos, current_pos + sample_count))
return;
@ -252,6 +267,8 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
temp_mixbuf = data->mixbuf;
temp_outbuf = outbuf;
current_subpos = current_pos;
/* apply mixes in order per channel */
for (s = 0; s < sample_count; s++) {
/* reset after new sample 'step'*/
@ -263,7 +280,7 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
}
for (m = 0; m < data->mixing_count; m++) {
mix_command_data mix = data->mixing_chain[m];
mix_command_data *mix = &data->mixing_chain[m];
/* mixing ops are designed to apply in order, all channels per 1 sample 'step'. Since some ops change
* total channels, channel number meaning varies as ops move them around, ex:
@ -274,34 +291,34 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
* - 2ch w/ "1-2,1d" = ch1<>ch2, ch1(drop and move ch2(old ch1) to ch1) = ch1
* - 2ch w/ "1d,1-2" = ch1(drop and pull rest), ch1(do nothing, ch2 doesn't exist now) = ch2
*/
switch(mix.command) {
switch(mix->command) {
case MIX_SWAP:
temp_f = stpbuf[mix.ch_dst];
stpbuf[mix.ch_dst] = stpbuf[mix.ch_src];
stpbuf[mix.ch_src] = temp_f;
temp_f = stpbuf[mix->ch_dst];
stpbuf[mix->ch_dst] = stpbuf[mix->ch_src];
stpbuf[mix->ch_src] = temp_f;
break;
case MIX_ADD:
stpbuf[mix.ch_dst] = stpbuf[mix.ch_dst] + stpbuf[mix.ch_src] * mix.vol;
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src] * mix->vol;
break;
case MIX_VOLUME:
if (mix.ch_dst < 0) {
if (mix->ch_dst < 0) {
for (ch = 0; ch < step_channels; ch++) {
stpbuf[ch] = stpbuf[ch] * mix.vol;
stpbuf[ch] = stpbuf[ch] * mix->vol;
}
}
else {
stpbuf[mix.ch_dst] = stpbuf[mix.ch_dst] * mix.vol;
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] * mix->vol;
}
break;
case MIX_LIMIT:
temp_max = limiter_max * mix.vol;
temp_min = limiter_min * mix.vol;
temp_max = limiter_max * mix->vol;
temp_min = limiter_min * mix->vol;
if (mix.ch_dst < 0) {
if (mix->ch_dst < 0) {
for (ch = 0; ch < step_channels; ch++) {
if (stpbuf[ch] > temp_max)
stpbuf[ch] = temp_max;
@ -310,47 +327,45 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
}
}
else {
if (stpbuf[mix.ch_dst] > temp_max)
stpbuf[mix.ch_dst] = temp_max;
else if (stpbuf[mix.ch_dst] < temp_min)
stpbuf[mix.ch_dst] = temp_min;
if (stpbuf[mix->ch_dst] > temp_max)
stpbuf[mix->ch_dst] = temp_max;
else if (stpbuf[mix->ch_dst] < temp_min)
stpbuf[mix->ch_dst] = temp_min;
}
break;
case MIX_UPMIX:
step_channels += 1;
for (ch = step_channels - 1; ch > mix.ch_dst; ch--) {
for (ch = step_channels - 1; ch > mix->ch_dst; ch--) {
stpbuf[ch] = stpbuf[ch-1]; /* 'push' channels forward (or pull backwards) */
}
stpbuf[mix.ch_dst] = 0; /* inserted as silent */
stpbuf[mix->ch_dst] = 0; /* inserted as silent */
break;
case MIX_DOWNMIX:
step_channels -= 1;
for (ch = mix.ch_dst; ch < step_channels; ch++) {
for (ch = mix->ch_dst; ch < step_channels; ch++) {
stpbuf[ch] = stpbuf[ch+1]; /* 'pull' channels back */
}
break;
case MIX_KILLMIX:
step_channels = mix.ch_dst; /* clamp channels */
step_channels = mix->ch_dst; /* clamp channels */
break;
case MIX_FADE:
current_subpos = current_pos + s;
ok = get_fade_gain(&mix, &cur_vol, current_subpos);
ok = get_fade_gain(mix, &cur_vol, current_subpos);
if (!ok) {
break;
break; /* fade doesn't apply right now */
}
if (mix.ch_dst < 0) {
if (mix->ch_dst < 0) {
for (ch = 0; ch < step_channels; ch++) {
stpbuf[ch] = stpbuf[ch] * cur_vol;
}
}
else {
stpbuf[mix.ch_dst] = stpbuf[mix.ch_dst] * cur_vol;
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] * cur_vol;
}
break;
@ -359,6 +374,8 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
}
}
current_subpos++;
temp_mixbuf += step_channels;
temp_outbuf += vgmstream->channels;
}

View File

@ -1049,6 +1049,7 @@ static int find_chunk_internal(STREAMFILE *streamFile, uint32_t chunk_id, off_t
while (offset < max_offset) {
uint32_t chunk_type = read_32bit_type(offset + 0x00,streamFile);
uint32_t chunk_size = read_32bit_size(offset + 0x04,streamFile);
//;VGM_LOG("CHUNK: type=%x, size=%x at %lx\n", chunk_type, chunk_size, offset);
if (chunk_type == chunk_id) {
if (out_chunk_offset) *out_chunk_offset = offset + 0x08;
@ -1074,6 +1075,12 @@ int find_chunk_le(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset,
int find_chunk(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, int full_chunk_size, off_t *out_chunk_offset, size_t *out_chunk_size, int big_endian_size, int zero_size_end) {
return find_chunk_internal(streamFile, chunk_id, start_offset, 0, full_chunk_size, out_chunk_offset, out_chunk_size, 1, big_endian_size, zero_size_end);
}
int find_chunk_riff_le(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, size_t max_size, off_t *out_chunk_offset, size_t *out_chunk_size) {
return find_chunk_internal(streamFile, chunk_id, start_offset, max_size, 0, out_chunk_offset, out_chunk_size, 1, 0, 0);
}
int find_chunk_riff_be(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, size_t max_size, off_t *out_chunk_offset, size_t *out_chunk_size) {
return find_chunk_internal(streamFile, chunk_id, start_offset, max_size, 0, out_chunk_offset, out_chunk_size, 1, 1, 0);
}
int find_chunk_riff_ve(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, size_t max_size, off_t *out_chunk_offset, size_t *out_chunk_size, int big_endian) {
return find_chunk_internal(streamFile, chunk_id, start_offset, max_size, 0, out_chunk_offset, out_chunk_size, big_endian, big_endian, 0);
}

View File

@ -268,6 +268,10 @@ int check_extensions(STREAMFILE *streamFile, const char * cmp_exts);
int find_chunk_be(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, int full_chunk_size, off_t *out_chunk_offset, size_t *out_chunk_size);
int find_chunk_le(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, int full_chunk_size, off_t *out_chunk_offset, size_t *out_chunk_size);
int find_chunk(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, int full_chunk_size, off_t *out_chunk_offset, size_t *out_chunk_size, int big_endian_size, int zero_size_end);
/* find a RIFF-style chunk (with chunk_size not including id and size) */
int find_chunk_riff_le(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, size_t max_size, off_t *out_chunk_offset, size_t *out_chunk_size);
int find_chunk_riff_be(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, size_t max_size, off_t *out_chunk_offset, size_t *out_chunk_size);
/* same with chunk ids in variable endianess (so instead of "fmt " has " tmf" */
int find_chunk_riff_ve(STREAMFILE *streamFile, uint32_t chunk_id, off_t start_offset, size_t max_size, off_t *out_chunk_offset, size_t *out_chunk_size, int big_endian);