mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 16:30:54 +01:00
commit
d05fa3a771
181
README.md
181
README.md
@ -96,7 +96,7 @@ and follow the above instructions for installing the other files needed.
|
||||
Note that this has less features compared to in_vgmstream and has no configuration.
|
||||
Since XMPlay supports Winamp plugins you may also use `in_vgmstream.dll` instead.
|
||||
|
||||
Because the XMPlay MP3 decoder incorrectly tries to play some vgmstream exts,
|
||||
Because the XMPlay MP3 decoder incorrectly tries to play some vgmstream extensions,
|
||||
you need to manually fix it by going to **options > plugins > input > vgmstream**
|
||||
and in the "priority filetypes" put: `ahx,asf,awc,ckd,fsb,genh,msf,p3d,rak,scd,txth,xvag`
|
||||
|
||||
@ -123,7 +123,7 @@ Usage: `vgmstream123 [options] INFILE ...`
|
||||
|
||||
The program is meant to be a simple stand-alone player, supporting playback
|
||||
of vgmstream files through libao. Files compressed with gzip/bzip2/xz also
|
||||
work, as identified by a .gz/.bz2/.xz extension. The file will be decompressed
|
||||
work, as identified by a `.gz/.bz2/.xz` extension. The file will be decompressed
|
||||
to a temp dir using the respective utility program (which must be installed
|
||||
and accessible) and then loaded.
|
||||
|
||||
@ -155,37 +155,37 @@ multiple .txtp (explained below) to select one of the subsongs (like `bgm.sxd#10
|
||||
|
||||
You can use this python script to autogenerate one `.txtp` per subsong:
|
||||
https://github.com/losnoco/vgmstream/tree/master/cli/txtp_maker.py
|
||||
Put in the same dir as test.exe/vgmstream_cli, then to drag-and-drop files with subsongs
|
||||
to `txtp_maker.py`.
|
||||
Put in the same dir as test.exe/vgmstream_cli, then to drag-and-drop files with
|
||||
subsongs to `txtp_maker.py`.
|
||||
|
||||
### Renamed files
|
||||
A few extensions that vgmstream supports clash with common ones. Since players
|
||||
like foobar or Winamp don't react well to that, they may be renamed to make
|
||||
them playable through vgmstream.
|
||||
- .aac to .laac (tri-Ace games)
|
||||
- .ac3 to .lac3 (standard AC3)
|
||||
- .aif to .laif or .aiffl or .aifcl (standard Mac AIF, Asobo AIF, Ogg)
|
||||
- .aiff/aifc to .aiffl/aifcl (standard Mac AIF)
|
||||
- .asf to .lasf (EA games, Argonaut ASF)
|
||||
- .bin to .lbin (various)
|
||||
- .flac to .lflac (standard FLAC)
|
||||
- .mp2 to .lmp2 (standard MP2)
|
||||
- .mp3 to .lmp3 (standard MP3)
|
||||
- .mp4 to .lmp4 (standard M4A)
|
||||
- .mpc to .lmpc (standard MPC)
|
||||
- .ogg to .logg (standard OGG)
|
||||
- .opus to .lopus (standard OPUS or Switch OPUS)
|
||||
- .stm to .lstm (Rockstar STM)
|
||||
- .wav to .lwav (standard WAV)
|
||||
- .wma to .lwma (standard WMA)
|
||||
- .(any) to .vgmstream (FFmpeg formats or TXTH)
|
||||
- `.aac` to `.laac` (tri-Ace games)
|
||||
- `.ac3` to `.lac3` (standard AC3)
|
||||
- `.aif` to `.laif` (standard Mac AIF, Asobo AIF, Ogg)
|
||||
- `.aiff/aifc` to `.laiffl/laifc` (standard Mac AIF)
|
||||
- `.asf` to `.lasf` (EA games, Argonaut ASF)
|
||||
- `.bin` to `.lbin` (various)
|
||||
- `.flac` to `.lflac` (standard FLAC)
|
||||
- `.mp2` to `.lmp2` (standard MP2)
|
||||
- `.mp3` to `.lmp3` (standard MP3)
|
||||
- `.mp4` to `.lmp4` (standard M4A)
|
||||
- `.mpc` to `.lmpc` (standard MPC)
|
||||
- `.ogg` to `.logg` (standard OGG)
|
||||
- `.opus` to `.lopus` (standard OPUS or Switch OPUS)
|
||||
- `.stm` to `.lstm` (Rockstar STM)
|
||||
- `.wav` to `.lwav` (standard WAV)
|
||||
- `.wma` to `.lwma` (standard WMA)
|
||||
- `.(any)` to `.vgmstream` (FFmpeg formats or TXTH)
|
||||
|
||||
Command line tools don't have this restriction and will accept the original
|
||||
filename.
|
||||
|
||||
The main advantage to rename them is that vgmstream may use the file's
|
||||
internal loop info, or apply subtle fixes, but is also limited in some ways
|
||||
(like standard/player's tagging). .vgmstream is a catch-all extension that
|
||||
(like standard/player's tagging). `.vgmstream` is a catch-all extension that
|
||||
may work as a last resort to make a file playable.
|
||||
|
||||
Some plugins have options that allow any extension (common or unknown) to be
|
||||
@ -209,54 +209,87 @@ vgmstream also supports audio from videos, but usually must be demuxed (extracte
|
||||
without modification) first, since vgmstream doesn't attempt to support them.
|
||||
|
||||
The easiest way to do this is using VGMToolBox's "Video Demultiplexer" option
|
||||
for common game video formats (.bik, .vp6, .pss, .pam, .pmf, .usm, .xmv, etc).
|
||||
for common game video formats (`.bik`, `.vp6`, `.pss`, `.pam`, `.pmf`, `.usm`, `.xmv`, etc).
|
||||
|
||||
For standard videos formats (.avi, .mp4, .webm, .m2v, .ogv, etc) not supported
|
||||
by VGMToolBox FFmpeg binary may work:
|
||||
For standard videos formats (`.avi`, `.mp4`, `.webm`, `.m2v`, `.ogv`, etc) not supported
|
||||
by VGMToolBox, FFmpeg binary may work:
|
||||
- `ffmpeg.exe -i (input file) -vn -acodec copy (output file)`
|
||||
Output extension may need to be adjusted to some appropriate audio file depending
|
||||
on the audio codec used. ffprobe.exe can list this codec, though the correct audio
|
||||
extension depends on the video itself (like .avi to .wav/mp2/mp3 or .ogv to .ogg).
|
||||
on the audio codec used. `ffprobe.exe` can list this codec, though the correct audio
|
||||
extension depends on the video itself (like `.avi` to `.wav/mp2/mp3` or `.ogv` to `.ogg`).
|
||||
|
||||
Some games use custom video formats, demuxer scripts in .bms format may be found
|
||||
Some games use custom video formats, demuxer scripts in `.bms` format may be found
|
||||
on the internet.
|
||||
|
||||
### Companion files
|
||||
Some formats have companion files with external looping info, and should be
|
||||
left together.
|
||||
- .mus (playlist for .acm)
|
||||
- .pos (loop info for .wav, and sometimes .ogg)
|
||||
- .ogg.sli or .sli (loop info for .ogg)
|
||||
- .ogg.sfl (loop info for .ogg)
|
||||
- .vgmstream.pos (loop info for FFmpeg formats)
|
||||
- also possible for certain extensions like .lflac.pos
|
||||
Some formats have companion files with external info, that should be left together:
|
||||
- `.mus`: playlist for `.acm`
|
||||
- `.ogg.sli` or `.sli`: loop info for `.ogg`
|
||||
- `.ogg.sfl` : loop info for `.ogg`
|
||||
- `.opus.sli`: loop info for `.opus`
|
||||
- `.pos`: loop info for .wav
|
||||
- `.vgmstream.pos`: loop info for FFmpeg formats
|
||||
- `.acb`: names for `.awb`
|
||||
- `.xsb`: names for `.xwb`
|
||||
|
||||
Similarly some formats split header and/or data in separate files (.sgh+sgd,
|
||||
.wav.str+.wav, (file)_L.dsp+(file)_R.dsp, etc). vgmstream will also detect
|
||||
and use those as needed and must be together, even if only one of the two
|
||||
will be used to play.
|
||||
Similarly some formats split header+body data in separate files, examples:
|
||||
- `.abk`+`.ast`
|
||||
- `.bnm`+`.apm/wav`
|
||||
- `.ktsl2asbin`+`.ktsl2stbin`
|
||||
- `.mih`+`.mib`
|
||||
- `.mpf`+`.mus`
|
||||
- `.pk`+`.spk`
|
||||
- `.sb0`+`.sp0` (or other numbers instead of `0`)
|
||||
- `.sgh`+`.sgd`
|
||||
- `.snr`+`.sns`
|
||||
- `.spt`+`.spd`
|
||||
- `.sts`+`.int`
|
||||
- `.xwh`+`.xwb`
|
||||
- `.xps`+`dat`
|
||||
- `.wav.str`+`.wav`
|
||||
- `.wav`+`.dcs`
|
||||
- `.wbh`+`.wbd`
|
||||
Both are needed to play and must be together. The usual rule is you open the
|
||||
bigger file (body), save a few formats where the smaller file is opened instead
|
||||
for technical reasons (mainly some bank formats).
|
||||
|
||||
Some formats may have companion files with different names which are hardcoded
|
||||
instead of being listed in the main file (e.g. .mpf+.mus). In these cases, you
|
||||
can use TXTM format to specify associated companion files. See below for more
|
||||
information.
|
||||
Generally companion files are named the same (`bgm.awb`+`bgm.acb`), or internally
|
||||
point to another file `sfx.sb0`+`STREAM.sb0`. A few formats may have different names
|
||||
which are hardcoded instead of being listed in the main file (e.g. `.mpf+.mus`).
|
||||
In these cases, you can use *TXTM* format to specify associated companion files.
|
||||
See *Artificial files* below for more information.
|
||||
|
||||
.pos is a small file with 32 bit little endian values: loop start sample
|
||||
and loop end sample. For FFmpeg formats (.vgmstream.pos) it may optionally
|
||||
have total samples after those.
|
||||
A special case of the above is "dual file stereo", where 2 similarly named mono
|
||||
files are fused together to make 1 stereo song.
|
||||
- `(file)_L.dsp`+`(file)_R.dsp`
|
||||
- `(file)-l.dsp`+`(file)-l.dsp`
|
||||
- `(file).L`+`(file).R`
|
||||
- `(file)_0.dsp`+`(file)_1.dsp`
|
||||
- `(file)_Left.dsp`+`(file)_Right.dsp`
|
||||
- `(file).v0`+`(file).v1`
|
||||
This is only allowed in a few formats (mainly `.dsp` and `.vag`). In those cases
|
||||
you can open either `L` or `R` and you'll get the same stereo song. If you rename
|
||||
one of the files the "pair" won't be found, and both will be played as mono.
|
||||
|
||||
`.pos` is a small file with 32 bit little endian values: loop start sample and
|
||||
loop end sample. This is a real format, but is sometimes reused to force loops.
|
||||
If you want to force looping files consider using *TXTP* instead, as it's much
|
||||
simpler to make and cleaner: for example create a text file named `bgm01-loop.txtp`
|
||||
and inside write `bgm01.mp3 #I 10.0 90.0`. Open the `.txtp` to play the `.mp3`
|
||||
looping from 10 to 90 seconds.
|
||||
|
||||
### Decryption keys
|
||||
Certain formats have encrypted data, and need a key to decrypt. vgmstream
|
||||
will try to find the correct key from a list, but it can be provided by
|
||||
a companion file:
|
||||
- .adx: .adxkey (keystring, 8 byte keycode, or derived 6 byte start/mult/add key)
|
||||
- .ahx: .ahxkey (derived 6 byte start/mult/add key)
|
||||
- .hca: .hcakey (8 byte decryption key, a 64-bit number)
|
||||
- `.adx`: `.adxkey` (keystring, 8 byte keycode, or derived 6 byte start/mult/add key)
|
||||
- `.ahx`: `.ahxkey` (derived 6 byte start/mult/add key)
|
||||
- `.hca`: `.hcakey` (8 byte decryption key, a 64-bit number)
|
||||
- May be followed by 2 byte AWB scramble key for newer HCA
|
||||
- .fsb: .fsbkey (decryption key, in hex)
|
||||
- .bnsf: .bnsfkey (decryption key, a string up to 24 chars)
|
||||
- `.fsb`: `.fsbkey` (decryption key, in hex)
|
||||
- `.bnsf`: `.bnsfkey` (decryption key, a string up to 24 chars)
|
||||
|
||||
The key file can be ".(ext)key" (for the whole folder), or "(name).(ext)key"
|
||||
The key file can be `.(ext)key` (for the whole folder), or `(name).(ext)key"
|
||||
(for a single file). The format is made up to suit vgmstream.
|
||||
|
||||
### Artificial files
|
||||
@ -267,7 +300,7 @@ Those can be played using an artificial header with info vgmstream needs.
|
||||
|
||||
**GENH**: a byte header placed right before the original data, modyfing it.
|
||||
The resulting file must be (name).genh. Contains static header data.
|
||||
Programs like VGMToolbox can help to create GENH.
|
||||
Programs like VGMToolbox can help to create *GENH*.
|
||||
|
||||
**TXTH**: a text header placed in an external file. The TXTH must be named
|
||||
`.txth` or `.(ext).txth` (for the whole folder), or `(name.ext).txth` (for a
|
||||
@ -277,22 +310,21 @@ file, or static values.
|
||||
*TXTH* is recomended over *GENH* as it's far easier to create and has many
|
||||
more functions.
|
||||
|
||||
|
||||
For files that already play, sometimes they are used by the game in various
|
||||
complex and non-standard ways, like playing multiple small songs as a single
|
||||
one, or using some channels as a section of the song. For those cases we
|
||||
can use create a *TXTP* file.
|
||||
can create a *TXTP* file.
|
||||
|
||||
**TXTP**: text files with player configuration, named `(name).txtp`. Text inside
|
||||
can contain a list of filenames to play as one (ex. `intro.vag(line)loop.vag`),
|
||||
list of separate channel files to join as a single multichannel file,
|
||||
subsong index (ex. `bgm.sxd#10`), per-file configurations like number of
|
||||
loops, remove unneeded channels, and many other features.
|
||||
loops, remove unneeded channels, make looping files, and many other features.
|
||||
|
||||
**TXTM**: text file named `.txtm` for formats with companion files. It lists
|
||||
name combos determining which companion files to load for each main file.
|
||||
It is useful for formats where name combos are hardcoded so vgmstream doesn't
|
||||
know which companion file(-s) to load if its name doesn't match the main file.
|
||||
know which companion file(s) to load if its name doesn't match the main file.
|
||||
Note that companion file order is usually important.
|
||||
|
||||
Usage example:
|
||||
@ -301,6 +333,10 @@ Usage example:
|
||||
entrance.mpf:entrance.mus,entrance_o.mus
|
||||
willow.mpf:willow.mus,willow_o.mus
|
||||
```
|
||||
```
|
||||
# Metal Gear Solid: Snake Eater 3D (3DS) names for .awb
|
||||
bgm_2_streamfiles.awb: bgm_2.acb
|
||||
```
|
||||
|
||||
Creation of those files is meant for advanced users, docs can be found in
|
||||
vgmstream source.
|
||||
@ -313,10 +349,10 @@ really opening it (should show "VGMSTREAM" somewhere in the file info), and
|
||||
try to remove a few other plugins.
|
||||
|
||||
foobar's FFmpeg plugin and foo_adpcm are known to cause issues, but in
|
||||
recent versions (1.4.x) you can configure plugin priority.
|
||||
modern versions (+1.4.x) you can configure plugin priority.
|
||||
|
||||
In Audacious, vgmstream is set with slightly higher priority than FFmpeg,
|
||||
since it steals many formats that you normally want to loop (like .adx).
|
||||
since it steals many formats that you normally want to loop (like `.adx`).
|
||||
However other plugins may set themselves higher, stealing formats instead.
|
||||
If current Audacious version doesn't let to change plugin priority you may
|
||||
need to disable some plugins (requires restart) or set priority on compile
|
||||
@ -335,6 +371,7 @@ since it can't guess how the file should be properly adjusted).
|
||||
|
||||
You can also choose which channels to play using *TXTP*. For example, create
|
||||
a file named `song.adx#C1,2.txtp` to play only channels 1 and 2 from `song.adx`.
|
||||
*TXTP* also has command to tweak how files is downmixed.
|
||||
|
||||
|
||||
## Tagging
|
||||
@ -400,8 +437,8 @@ enabled in preferences):
|
||||
```
|
||||
|
||||
### TXTP matching
|
||||
To ease *TXTP* config, tags with plain files will match .txtp with config, and tags
|
||||
with .txtp config also match plain files:
|
||||
To ease *TXTP* config, tags with plain files will match `.txtp` with config, and tags
|
||||
with `.txtp` config also match plain files:
|
||||
**!tags.m3u**
|
||||
```
|
||||
# @TITLE Title1
|
||||
@ -441,7 +478,7 @@ BGM01.adx #I 1.0 90.0 .txtp
|
||||
|
||||
### Issues
|
||||
If your player isn't picking tags make sure vgmstream is detecting the song
|
||||
(as other plugins can steal its extensions, see above), .m3u is properly
|
||||
(as other plugins can steal its extensions, see above), `.m3u` is properly
|
||||
named and that filenames inside match the song filename. For Winamp you need
|
||||
to make sure *options > titles > advanced title formatting* checkbox is set and
|
||||
the format defined.
|
||||
@ -453,11 +490,11 @@ When tags change behavior varies depending on player:
|
||||
- *Audacious*: files need to be readded to the playlist
|
||||
|
||||
Currently there is no tool to aid in the creation of these tags, but you can create
|
||||
a base .m3u and edit as a text file.
|
||||
a base `.m3u` and edit as a text file.
|
||||
|
||||
vgmstream's "m3u tagging" is meant to be simple to make and share (just a text
|
||||
file), easier to support in multiple players (rather than needing a custom plugin),
|
||||
allow OST-like ordering but also combinable with other .m3u, and be flexible enough
|
||||
allow OST-like ordering but also combinable with other `.m3u`, and be flexible enough
|
||||
to have commands. If you are not satisfied with vgmstream's tagging format,
|
||||
foobar2000 has other plugins (with write support) that may be of use:
|
||||
- m-TAGS: http://www.m-tags.org/
|
||||
@ -465,14 +502,14 @@ foobar2000 has other plugins (with write support) that may be of use:
|
||||
|
||||
|
||||
## Virtual TXTP files
|
||||
Some of vgmstream's plugins allow you to use virtual .txtp files, that combined
|
||||
Some of vgmstream's plugins allow you to use virtual `.txtp` files, that combined
|
||||
with playlists let you make quick song configs.
|
||||
|
||||
Normally you can create a physical .txtp file that points to another file with
|
||||
config, and .txtp have a "mini-txtp" mode that configures files with only the
|
||||
config, and `.txtp` have a "mini-txtp" mode that configures files with only the
|
||||
filename.
|
||||
|
||||
Instead of manually creating .txtp files you can put non-existing virtual .txtp
|
||||
Instead of manually creating `.txtp` files you can put non-existing virtual `.txtp`
|
||||
in a `.m3u` playlist:
|
||||
```
|
||||
# playlist that opens subsongs directly without having to create .txtp
|
||||
@ -502,14 +539,18 @@ You can also use it in CLI for quick access to some txtp-exclusive functions:
|
||||
test.exe -o btl_koopa1_44k_lp.wav "btl_koopa1_44k_lp.brstm #h22050.txtp"
|
||||
```
|
||||
|
||||
Support for this feature is limited by player itself, as foobar and Winamp allow
|
||||
non-existant files referenced in a `.m3u`, while other players may filter them
|
||||
first.
|
||||
|
||||
|
||||
## Supported codec types
|
||||
Quick list of codecs vgmstream supports, including many obscure ones that
|
||||
are used in few games.
|
||||
|
||||
- PCM 16-bit
|
||||
- PCM 8-bit (signed/unsigned)
|
||||
- PCM 4-bit (signed/unsigned)
|
||||
- PCM 8-bit (signed, unsigned)
|
||||
- PCM 4-bit (signed, unsigned)
|
||||
- PCM 32-bit float
|
||||
- u-Law/a-LAW
|
||||
- CRI ADX (standard, fixed, exponential, encrypted)
|
||||
|
@ -1,181 +1,181 @@
|
||||
#include "coding.h"
|
||||
#include "../util.h"
|
||||
|
||||
#if 0
|
||||
/* known game code/platforms use float buffer and coefs, but some approximations around use this int math:
|
||||
* ...
|
||||
* coef1 = table[index + 0]
|
||||
* coef2 = table[index + 4]
|
||||
* sample = clamp16(((signed_nibble << (20 - shift)) + hist1 * coef1 + hist2 * coef2 + 128) >> 8); */
|
||||
static const int EA_XA_TABLE[20] = {
|
||||
0, 240, 460, 392,
|
||||
0, 0, -208, -220,
|
||||
0, 1, 3, 4,
|
||||
7, 8, 10, 11,
|
||||
0, -1, -3, -4
|
||||
};
|
||||
#endif
|
||||
|
||||
/* standard CD-XA's K0/K1 filter pairs */
|
||||
static const float xa_coefs[16][2] = {
|
||||
{ 0.0, 0.0 },
|
||||
{ 0.9375, 0.0 },
|
||||
{ 1.796875, -0.8125 },
|
||||
{ 1.53125, -0.859375 },
|
||||
/* only 4 pairs exist, assume 0s for bad indexes */
|
||||
};
|
||||
|
||||
/* EA-XAS v1, evolution of EA-XA/XAS and cousin of MTA2. Reverse engineered from various .exes/.so
|
||||
*
|
||||
* Layout: blocks of 0x4c per channel (128 samples), divided into 4 headers + 4 vertical groups of 15 bytes.
|
||||
* Original code reads all headers first then processes all nibbles (for CPU cache/parallelism/SIMD optimizations).
|
||||
* To simplify, always decodes the block and discards unneeded samples, so doesn't use external hist. */
|
||||
void decode_ea_xas_v1(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
uint8_t frame[0x4c] = {0};
|
||||
off_t frame_offset;
|
||||
int group, row, i, samples_done = 0, sample_count = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
|
||||
|
||||
/* internal interleave */
|
||||
bytes_per_frame = 0x4c;
|
||||
samples_per_frame = 128;
|
||||
first_sample = first_sample % samples_per_frame;
|
||||
|
||||
frame_offset = stream->offset + bytes_per_frame * channel;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
|
||||
//todo: original code uses float sample buffer:
|
||||
//- header pcm-hist to float-hist: hist * (1/32768)
|
||||
//- nibble to signed to float: (int32_t)(pnibble << 28) * SHIFT_MUL_LUT[shift_index]
|
||||
// look-up table just simplifies ((nibble << 12 << 12) >> 12 + shift) * (1/32768)
|
||||
// though maybe introduces rounding errors?
|
||||
//- coefs apply normally, though hists are already floats
|
||||
//- final float sample isn't clamped
|
||||
|
||||
|
||||
/* parse group headers */
|
||||
for (group = 0; group < 4; group++) {
|
||||
float coef1, coef2;
|
||||
int16_t hist1, hist2;
|
||||
uint8_t shift;
|
||||
uint32_t group_header = (uint32_t)get_32bitLE(frame + group*0x4); /* always LE */
|
||||
|
||||
coef1 = xa_coefs[group_header & 0x0F][0];
|
||||
coef2 = xa_coefs[group_header & 0x0F][1];
|
||||
hist2 = (int16_t)((group_header >> 0) & 0xFFF0);
|
||||
hist1 = (int16_t)((group_header >> 16) & 0xFFF0);
|
||||
shift = (group_header >> 16) & 0x0F;
|
||||
|
||||
/* write header samples (needed) */
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist2;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist1;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
/* process nibbles per group */
|
||||
for (row = 0; row < 15; row++) {
|
||||
for (i = 0; i < 1*2; i++) {
|
||||
uint8_t nibbles = frame[4*4 + row*0x04 + group + i/2];
|
||||
int sample;
|
||||
|
||||
sample = i&1 ? /* high nibble first */
|
||||
(nibbles >> 0) & 0x0f :
|
||||
(nibbles >> 4) & 0x0f;
|
||||
sample = (int16_t)(sample << 12) >> shift; /* 16b sign extend + scale */
|
||||
sample = sample + hist1 * coef1 + hist2 * coef2;
|
||||
sample = clamp16(sample);
|
||||
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = sample;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* internal interleave (interleaved channels, but manually advances to co-exist with ea blocks) */
|
||||
if (first_sample + samples_done == samples_per_frame) {
|
||||
stream->offset += bytes_per_frame * channelspacing;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* EA-XAS v0, without complex layouts and closer to EA-XA. Somewhat based on daemon1's decoder */
|
||||
void decode_ea_xas_v0(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
uint8_t frame[0x13] = {0};
|
||||
off_t frame_offset;
|
||||
int i, frames_in, samples_done = 0, sample_count = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
|
||||
|
||||
/* external interleave (fixed size), mono */
|
||||
bytes_per_frame = 0x02 + 0x02 + 0x0f;
|
||||
samples_per_frame = 1 + 1 + 0x0f*2;
|
||||
frames_in = first_sample / samples_per_frame;
|
||||
first_sample = first_sample % samples_per_frame;
|
||||
|
||||
frame_offset = stream->offset + bytes_per_frame * frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
|
||||
//todo see above
|
||||
|
||||
/* process frame */
|
||||
{
|
||||
float coef1, coef2;
|
||||
int16_t hist1, hist2;
|
||||
uint8_t shift;
|
||||
uint32_t frame_header = (uint32_t)get_32bitLE(frame); /* always LE */
|
||||
|
||||
coef1 = xa_coefs[frame_header & 0x0F][0];
|
||||
coef2 = xa_coefs[frame_header & 0x0F][1];
|
||||
hist2 = (int16_t)((frame_header >> 0) & 0xFFF0);
|
||||
hist1 = (int16_t)((frame_header >> 16) & 0xFFF0);
|
||||
shift = (frame_header >> 16) & 0x0F;
|
||||
|
||||
/* write header samples (needed) */
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist2;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist1;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
/* process nibbles */
|
||||
for (i = 0; i < 0x0f*2; i++) {
|
||||
uint8_t nibbles = frame[0x02 + 0x02 + i/2];
|
||||
int sample;
|
||||
|
||||
sample = i&1 ? /* high nibble first */
|
||||
(nibbles >> 0) & 0x0f :
|
||||
(nibbles >> 4) & 0x0f;
|
||||
sample = (int16_t)(sample << 12) >> shift; /* 16b sign extend + scale */
|
||||
sample = sample + hist1 * coef1 + hist2 * coef2;
|
||||
sample = clamp16(sample);
|
||||
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = sample;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
#include "coding.h"
|
||||
#include "../util.h"
|
||||
|
||||
#if 0
|
||||
/* known game code/platforms use float buffer and coefs, but some approximations around use this int math:
|
||||
* ...
|
||||
* coef1 = table[index + 0]
|
||||
* coef2 = table[index + 4]
|
||||
* sample = clamp16(((signed_nibble << (20 - shift)) + hist1 * coef1 + hist2 * coef2 + 128) >> 8); */
|
||||
static const int EA_XA_TABLE[20] = {
|
||||
0, 240, 460, 392,
|
||||
0, 0, -208, -220,
|
||||
0, 1, 3, 4,
|
||||
7, 8, 10, 11,
|
||||
0, -1, -3, -4
|
||||
};
|
||||
#endif
|
||||
|
||||
/* standard CD-XA's K0/K1 filter pairs */
|
||||
static const float xa_coefs[16][2] = {
|
||||
{ 0.0, 0.0 },
|
||||
{ 0.9375, 0.0 },
|
||||
{ 1.796875, -0.8125 },
|
||||
{ 1.53125, -0.859375 },
|
||||
/* only 4 pairs exist, assume 0s for bad indexes */
|
||||
};
|
||||
|
||||
/* EA-XAS (XA Seekable) Version 1, evolution of EA-XA/XAS and cousin of MTA2. Reverse engineered from various .exes/.so
|
||||
*
|
||||
* Layout: blocks of 0x4c per channel (128 samples), divided into 4 headers + 4 vertical groups of 15 bytes.
|
||||
* Original code reads all headers first then processes all nibbles (for CPU cache/parallelism/SIMD optimizations).
|
||||
* To simplify, always decodes the block and discards unneeded samples, so doesn't use external hist. */
|
||||
void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
uint8_t frame[0x4c] = {0};
|
||||
off_t frame_offset;
|
||||
int group, row, i, samples_done = 0, sample_count = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
|
||||
|
||||
/* internal interleave */
|
||||
bytes_per_frame = 0x4c;
|
||||
samples_per_frame = 128;
|
||||
first_sample = first_sample % samples_per_frame;
|
||||
|
||||
frame_offset = stream->offset + bytes_per_frame * channel;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
|
||||
//todo: original code uses float sample buffer:
|
||||
//- header pcm-hist to float-hist: hist * (1/32768)
|
||||
//- nibble to signed to float: (int32_t)(pnibble << 28) * SHIFT_MUL_LUT[shift_index]
|
||||
// look-up table just simplifies ((nibble << 12 << 12) >> 12 + shift) * (1/32768)
|
||||
// though maybe introduces rounding errors?
|
||||
//- coefs apply normally, though hists are already floats
|
||||
//- final float sample isn't clamped
|
||||
|
||||
|
||||
/* parse group headers */
|
||||
for (group = 0; group < 4; group++) {
|
||||
float coef1, coef2;
|
||||
int16_t hist1, hist2;
|
||||
uint8_t shift;
|
||||
uint32_t group_header = (uint32_t)get_32bitLE(frame + group*0x4); /* always LE */
|
||||
|
||||
coef1 = xa_coefs[group_header & 0x0F][0];
|
||||
coef2 = xa_coefs[group_header & 0x0F][1];
|
||||
hist2 = (int16_t)((group_header >> 0) & 0xFFF0);
|
||||
hist1 = (int16_t)((group_header >> 16) & 0xFFF0);
|
||||
shift = (group_header >> 16) & 0x0F;
|
||||
|
||||
/* write header samples (needed) */
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist2;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist1;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
/* process nibbles per group */
|
||||
for (row = 0; row < 15; row++) {
|
||||
for (i = 0; i < 1*2; i++) {
|
||||
uint8_t nibbles = frame[4*4 + row*0x04 + group + i/2];
|
||||
int sample;
|
||||
|
||||
sample = i&1 ? /* high nibble first */
|
||||
(nibbles >> 0) & 0x0f :
|
||||
(nibbles >> 4) & 0x0f;
|
||||
sample = (int16_t)(sample << 12) >> shift; /* 16b sign extend + scale */
|
||||
sample = sample + hist1 * coef1 + hist2 * coef2;
|
||||
sample = clamp16(sample);
|
||||
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = sample;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* internal interleave (interleaved channels, but manually advances to co-exist with ea blocks) */
|
||||
if (first_sample + samples_done == samples_per_frame) {
|
||||
stream->offset += bytes_per_frame * channelspacing;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* EA-XAS v0 (xas0), without complex layouts and closer to EA-XA. Somewhat based on daemon1's decoder. */
|
||||
void decode_ea_xas_v0(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
uint8_t frame[0x13] = {0};
|
||||
off_t frame_offset;
|
||||
int i, frames_in, samples_done = 0, sample_count = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
|
||||
|
||||
/* external interleave (fixed size), mono */
|
||||
bytes_per_frame = 0x02 + 0x02 + 0x0f;
|
||||
samples_per_frame = 1 + 1 + 0x0f*2;
|
||||
frames_in = first_sample / samples_per_frame;
|
||||
first_sample = first_sample % samples_per_frame;
|
||||
|
||||
frame_offset = stream->offset + bytes_per_frame * frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
|
||||
//todo see above
|
||||
|
||||
/* process frame */
|
||||
{
|
||||
float coef1, coef2;
|
||||
int16_t hist1, hist2;
|
||||
uint8_t shift;
|
||||
uint32_t frame_header = (uint32_t)get_32bitLE(frame); /* always LE */
|
||||
|
||||
coef1 = xa_coefs[frame_header & 0x0F][0];
|
||||
coef2 = xa_coefs[frame_header & 0x0F][1];
|
||||
hist2 = (int16_t)((frame_header >> 0) & 0xFFF0);
|
||||
hist1 = (int16_t)((frame_header >> 16) & 0xFFF0);
|
||||
shift = (frame_header >> 16) & 0x0F;
|
||||
|
||||
/* write header samples (needed) */
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist2;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist1;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
/* process nibbles */
|
||||
for (i = 0; i < 0x0f*2; i++) {
|
||||
uint8_t nibbles = frame[0x02 + 0x02 + i/2];
|
||||
int sample;
|
||||
|
||||
sample = i&1 ? /* high nibble first */
|
||||
(nibbles >> 0) & 0x0f :
|
||||
(nibbles >> 4) & 0x0f;
|
||||
sample = (int16_t)(sample << 12) >> shift; /* 16b sign extend + scale */
|
||||
sample = sample + hist1 * coef1 + hist2 * coef2;
|
||||
sample = clamp16(sample);
|
||||
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = sample;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -345,6 +345,7 @@ static const char* extension_list[] = {
|
||||
"nop",
|
||||
"nps",
|
||||
"npsf", //fake extension/header id for .nps (in bigfiles)
|
||||
"nsopus",
|
||||
"nub",
|
||||
"nub2",
|
||||
"nus3audio",
|
||||
|
@ -26,7 +26,7 @@ void render_vgmstream_flat(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vg
|
||||
samples_to_do = sample_count - samples_written;
|
||||
|
||||
if (samples_to_do == 0) { /* when decoding more than num_samples */
|
||||
VGM_LOG("FLAT: samples_to_do 0\n");
|
||||
VGM_LOG_ONCE("FLAT: samples_to_do 0\n");
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
|
@ -145,6 +145,6 @@ void render_vgmstream_interleave(sample_t * buffer, int32_t sample_count, VGMSTR
|
||||
}
|
||||
return;
|
||||
fail:
|
||||
VGM_LOG("layout_interleave: wrong values found\n");
|
||||
VGM_LOG_ONCE("layout_interleave: wrong values found\n");
|
||||
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t));
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
|
||||
samples_to_do = sample_count - samples_written;
|
||||
|
||||
if (samples_to_do <= 0) { /* when decoding more than num_samples */
|
||||
VGM_LOG("LAYERED: samples_to_do 0\n");
|
||||
VGM_LOG_ONCE("LAYERED: samples_to_do 0\n");
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
}
|
||||
|
||||
if (data->current_segment >= data->segment_count) {
|
||||
VGM_LOG("SEGMENT: wrong current segment\n");
|
||||
VGM_LOG_ONCE("SEGMENT: wrong current segment\n");
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
data->current_segment++;
|
||||
|
||||
if (data->current_segment >= data->segment_count) { /* when decoding more than num_samples */
|
||||
VGM_LOG("SEGMENTED: reached last segment\n");
|
||||
VGM_LOG_ONCE("SEGMENTED: reached last segment\n");
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
samples_to_do = VGMSTREAM_SEGMENT_SAMPLE_BUFFER;
|
||||
|
||||
if (samples_to_do < 0) { /* 0 is ok? */
|
||||
VGM_LOG("SEGMENTED: wrong samples_to_do %i found\n", samples_to_do);
|
||||
VGM_LOG_ONCE("SEGMENTED: wrong samples_to_do %i found\n", samples_to_do);
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -15,11 +15,11 @@ VGMSTREAM* init_vgmstream_acb(STREAMFILE* sf) {
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "acb"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,sf) != 0x40555446) /* "@UTF" */
|
||||
if (read_u32be(0x00,sf) != 0x40555446) /* "@UTF" */
|
||||
goto fail;
|
||||
|
||||
/* .acb is a cue sheet that uses @UTF (CRI's generic table format) to store row/columns
|
||||
* with complex info (cues, sequences, spatial info, etc). it can store a memory .awb
|
||||
* with complex info (cues, sequences, spatial info, etc). It can store a memory .awb
|
||||
* (our target here), or reference external/streamed .awb (loaded elsewhere)
|
||||
* we only want .awb with actual waves but may use .acb to get names */
|
||||
{
|
||||
@ -34,8 +34,6 @@ VGMSTREAM* init_vgmstream_acb(STREAMFILE* sf) {
|
||||
if (rows != 1 || strcmp(name, "Header") != 0)
|
||||
goto fail;
|
||||
|
||||
//todo acb+cpk is also possible
|
||||
|
||||
if (!utf_query_data(utf, 0, "AwbFile", &offset, &size))
|
||||
goto fail;
|
||||
|
||||
@ -52,10 +50,16 @@ VGMSTREAM* init_vgmstream_acb(STREAMFILE* sf) {
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, "awb");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_awb_memory(temp_sf, sf);
|
||||
if (!vgmstream) goto fail;
|
||||
if (read_u32be(0x00, temp_sf) == 0x43504B20) { /* "CPK " */
|
||||
vgmstream = init_vgmstream_cpk_memory(temp_sf, sf); /* older */
|
||||
if (!vgmstream) goto fail;
|
||||
}
|
||||
else {
|
||||
vgmstream = init_vgmstream_awb_memory(temp_sf, sf); /* newer */
|
||||
if (!vgmstream) goto fail;
|
||||
}
|
||||
|
||||
/* name-loading for this for memory .awb will be called from init_vgmstream_awb_memory */
|
||||
/* name-loading for this for memory .awb will be called from init_vgmstream_awb/cpk_memory */
|
||||
|
||||
utf_close(utf);
|
||||
close_streamfile(temp_sf);
|
||||
@ -271,11 +275,12 @@ static int load_acb_synth(acb_header* acb, int16_t Index) {
|
||||
|
||||
acb->synth_depth++;
|
||||
|
||||
if (acb->synth_depth > 2) {
|
||||
/* sometimes 2 (ex. Yakuza 6) or even 3 (Street Fighter vs Tekken) */
|
||||
if (acb->synth_depth > 3) {
|
||||
VGM_LOG("ACB: Synth depth too high\n");
|
||||
goto fail; /* max Synth > Synth > Waveform (ex. Yakuza 6) */
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
//todo .CommandIndex > CommandTable
|
||||
//todo .TrackValues > TrackTable?
|
||||
|
||||
@ -351,7 +356,7 @@ static int load_acb_command_tlvs(acb_header* acb, STREAMFILE* sf, uint32_t Comma
|
||||
tlv_code = read_u16be(offset + 0x00, sf);
|
||||
tlv_size = read_u8 (offset + 0x02, sf);
|
||||
offset += 0x03;
|
||||
|
||||
|
||||
/* There are around 160 codes (some unused), with things like set volume, pan, stop, mute, and so on.
|
||||
* Multiple commands are linked and only "note on" seems to point so other objects, so maybe others
|
||||
* apply to current object (since there is "note off" without reference. */
|
||||
@ -533,7 +538,7 @@ static int load_acb_block(acb_header* acb, int16_t Index) {
|
||||
VGM_LOG("ACB: wrong Block.TrackIndex size\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
//todo .ActionTrackStartIndex/NumActionTracks > ?
|
||||
|
||||
/* read Tracks inside Block */
|
||||
|
@ -179,7 +179,7 @@ static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstre
|
||||
int len_name, len_cmp;
|
||||
|
||||
/* try parsing TXTM if present */
|
||||
sf_acb = read_filemap_file(sf_acb, 0);
|
||||
sf_acb = read_filemap_file(sf, 0);
|
||||
|
||||
/* try (name).awb + (name).awb */
|
||||
if (!sf_acb) {
|
||||
|
@ -164,7 +164,7 @@ fail:
|
||||
}
|
||||
|
||||
|
||||
/* BKHD mini format, probably from FX generator plugins [Borderlands 2 (X360), Warhammer 40000 (PC)] */
|
||||
/* BKHD mini format, for FX plugins [Borderlands 2 (X360), Warhammer 40000 (PC)] */
|
||||
VGMSTREAM* init_vgmstream_bkhd_fx(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset, data_size;
|
||||
@ -173,30 +173,48 @@ VGMSTREAM* init_vgmstream_bkhd_fx(STREAMFILE* sf) {
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf,"wem,bnk")) /* assumed */
|
||||
/* .wem: used when (rarely) external */
|
||||
if (!check_extensions(sf,"wem,bnk"))
|
||||
goto fail;
|
||||
big_endian = guess_endianness32bit(0x00, sf);
|
||||
read_u32 = big_endian ? read_u32be : read_u32le;
|
||||
|
||||
if (read_u32(0x00, sf) != 0x0400) /* codec? */
|
||||
goto fail;
|
||||
if (read_u32(0x04, sf) != 0x0800) /* codec? */
|
||||
goto fail;
|
||||
sample_rate = read_u32(0x08, sf);
|
||||
channels = read_u32(0x0c, sf) & 0xFF; /* 0x31 at 0x0d in PC, field is 32b vs X360 */
|
||||
/* 0x10: some id or small size? (related to entries?) */
|
||||
/* 0x14/18: some float? */
|
||||
entries = read_u32(0x1c, sf);
|
||||
/* 0x20 data size / 0x10 */
|
||||
/* 0x24 usually 4, sometimes higher values? */
|
||||
/* 0x30: unknown table of 16b that goes up and down, or is fixed */
|
||||
/* Not an actual stream but typically convolution reverb models and other FX plugin helpers.
|
||||
* Useless but to avoid "subsong not playing" complaints. */
|
||||
|
||||
if (read_u32(0x00, sf) == 0x0400 &&
|
||||
read_u32(0x04, sf) == 0x0800) {
|
||||
sample_rate = read_u32(0x08, sf);
|
||||
channels = read_u32(0x0c, sf) & 0xFF; /* 0x31 at 0x0d in PC, field is 32b vs X360 */
|
||||
/* 0x10: some id or small size? (related to entries?) */
|
||||
/* 0x14/18: some float? */
|
||||
entries = read_u32(0x1c, sf);
|
||||
/* 0x20 data size / 0x10 */
|
||||
/* 0x24 usually 4, sometimes higher values? */
|
||||
/* 0x30: unknown table of 16b that goes up and down, or is fixed */
|
||||
|
||||
start_offset = 0x30 + align_size_to_block(entries * 0x02, 0x10);
|
||||
data_size = get_streamfile_size(sf) - start_offset;
|
||||
}
|
||||
else if (read_u32be(0x04, sf) == 0x00004844 && /* floats actually? */
|
||||
read_u32be(0x08, sf) == 0x0000FA45 &&
|
||||
read_u32be(0x1c, sf) == 0x80000000) {
|
||||
/* seen in Crucible banks */
|
||||
sample_rate = 48000; /* meh */
|
||||
channels = 1;
|
||||
|
||||
start_offset = 0;
|
||||
data_size = get_streamfile_size(sf);
|
||||
big_endian = 0;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
start_offset = 0x30 + align_size_to_block(entries * 0x02, 0x10);
|
||||
data_size = get_streamfile_size(sf) - start_offset;
|
||||
loop_flag = 0;
|
||||
|
||||
/* output sounds a bit funny, maybe not an actual stream but sections or models for reverb/etc,
|
||||
* data seems divided in chunks of 0x2000 */
|
||||
|
||||
/* data seems divided in chunks of 0x2000 */
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
@ -212,7 +230,7 @@ VGMSTREAM* init_vgmstream_bkhd_fx(STREAMFILE* sf) {
|
||||
|
||||
vgmstream->num_samples = pcm_bytes_to_samples(data_size, channels, 32);
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,sf,start_offset))
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
|
248
src/meta/cpk.c
Normal file
248
src/meta/cpk.c
Normal file
@ -0,0 +1,248 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "cri_utf.h"
|
||||
|
||||
|
||||
typedef enum { HCA, CWAV, } cpk_type_t;
|
||||
|
||||
static void load_cpk_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid);
|
||||
|
||||
/* CPK - CRI container as audio bank [Metal Gear Solid: Snake Eater 3D (3DS), Street Fighter X Tekken (X360), Ace Combat Infinity (PS3)] */
|
||||
VGMSTREAM* init_vgmstream_cpk(STREAMFILE* sf) {
|
||||
return init_vgmstream_cpk_memory(sf, NULL);
|
||||
}
|
||||
|
||||
VGMSTREAM* init_vgmstream_cpk_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
off_t subfile_offset = 0;
|
||||
size_t subfile_size = 0;
|
||||
utf_context* utf = NULL;
|
||||
int total_subsongs, target_subsong = sf->stream_index;
|
||||
int subfile_id = 0;
|
||||
cpk_type_t type;
|
||||
const char* extension = NULL;
|
||||
uint32_t* sizes = NULL;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "awb"))
|
||||
goto fail;
|
||||
if (read_u32be(0x00,sf) != 0x43504B20) /* "CPK " */
|
||||
goto fail;
|
||||
if (read_u32be(0x10,sf) != 0x40555446) /* "@UTF" */
|
||||
goto fail;
|
||||
/* 04: 0xFF? */
|
||||
/* 08: 0x02A0? */
|
||||
/* 0c: null? */
|
||||
|
||||
/* CPK .cpk is CRI's generic file container, but here we only support CPK .awb used as
|
||||
* early audio bank, that like standard AFS2 .awb comes with .acb */
|
||||
{
|
||||
int rows, i;
|
||||
const char* name;
|
||||
const char* Tvers;
|
||||
uint32_t table_offset = 0, offset;
|
||||
uint32_t Files = 0, FilesL = 0, FilesH = 0;
|
||||
uint64_t ContentOffset = 0, ItocOffset = 0;
|
||||
uint16_t Align = 0;
|
||||
uint32_t DataL_offset = 0, DataL_size = 0, DataH_offset = 0, DataH_size = 0;
|
||||
|
||||
/* base header */
|
||||
table_offset = 0x10;
|
||||
utf = utf_open(sf, table_offset, &rows, &name);
|
||||
if (!utf || strcmp(name, "CpkHeader") != 0 || rows != 1)
|
||||
goto fail;
|
||||
|
||||
if (!utf_query_string(utf, 0, "Tvers", &Tvers) ||
|
||||
!utf_query_u32(utf, 0, "Files", &Files) ||
|
||||
!utf_query_u64(utf, 0, "ContentOffset", &ContentOffset) || /* absolute */
|
||||
!utf_query_u64(utf, 0, "ItocOffset", &ItocOffset) || /* Toc seems used for regular files */
|
||||
!utf_query_u16(utf, 0, "Align", &Align))
|
||||
goto fail;
|
||||
|
||||
utf_close(utf);
|
||||
utf = NULL;
|
||||
|
||||
if (strncmp(Tvers, "awb", 3) != 0) /* starts with "awb" + ".(version)" (SFvTK, MGS3D) or " for (version)" (ACI) */
|
||||
goto fail;
|
||||
if (Files <= 0)
|
||||
goto fail;
|
||||
|
||||
|
||||
/* Itoc header (regular .CPK tend to use Toc or Etoc header) */
|
||||
table_offset = 0x10 + ItocOffset;
|
||||
utf = utf_open(sf, table_offset, &rows, &name);
|
||||
if (!utf) goto fail;
|
||||
|
||||
if (rows != 1 || strcmp(name, "CpkItocInfo") != 0)
|
||||
goto fail;
|
||||
|
||||
if (!utf_query_u32(utf, 0, "FilesL", &FilesL) ||
|
||||
!utf_query_u32(utf, 0, "FilesH", &FilesH) ||
|
||||
!utf_query_data(utf, 0, "DataL", &DataL_offset, &DataL_size) || /* absolute */
|
||||
!utf_query_data(utf, 0, "DataH", &DataH_offset, &DataH_size)) /* absolute */
|
||||
goto fail;
|
||||
|
||||
utf_close(utf);
|
||||
utf = NULL;
|
||||
|
||||
|
||||
/* For maximum annoyance there are 2 tables (small+big files) that only list sizes,
|
||||
* and files can be mixed (small small big small big).
|
||||
* Must pre-read all entries to find actual offset plus subsongs number. */
|
||||
if (FilesL + FilesH != Files)
|
||||
goto fail;
|
||||
|
||||
total_subsongs = Files;
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong > total_subsongs || total_subsongs <= 0) goto fail;
|
||||
|
||||
sizes = calloc(Files, sizeof(uint32_t));
|
||||
if (!sizes) goto fail;
|
||||
|
||||
/* DataL header */
|
||||
table_offset = DataL_offset;
|
||||
utf = utf_open(sf, table_offset, &rows, &name);
|
||||
if (!utf || strcmp(name, "CpkItocL") != 0 || rows != FilesL)
|
||||
goto fail;
|
||||
|
||||
for (i = 0; i < rows; i++) {
|
||||
uint16_t ID = 0;
|
||||
uint16_t FileSize, ExtractSize;
|
||||
|
||||
if (!utf_query_u16(utf, i, "ID", &ID) ||
|
||||
!utf_query_u16(utf, i, "FileSize", &FileSize) ||
|
||||
!utf_query_u16(utf, i, "ExtractSize", &ExtractSize))
|
||||
goto fail;
|
||||
|
||||
if (ID >= Files || FileSize != ExtractSize || sizes[ID])
|
||||
goto fail;
|
||||
|
||||
sizes[ID] = FileSize;
|
||||
}
|
||||
|
||||
utf_close(utf);
|
||||
utf = NULL;
|
||||
|
||||
/* DataR header */
|
||||
table_offset = DataH_offset;
|
||||
utf = utf_open(sf, table_offset, &rows, &name);
|
||||
if (!utf || strcmp(name, "CpkItocH") != 0 || rows != FilesH)
|
||||
goto fail;
|
||||
|
||||
for (i = 0; i < rows; i++) {
|
||||
uint16_t ID = 0;
|
||||
uint32_t FileSize, ExtractSize;
|
||||
|
||||
if (!utf_query_u16(utf, i, "ID", &ID) ||
|
||||
!utf_query_u32(utf, i, "FileSize", &FileSize) ||
|
||||
!utf_query_u32(utf, i, "ExtractSize", &ExtractSize))
|
||||
goto fail;
|
||||
|
||||
if (ID >= Files || FileSize != ExtractSize || sizes[ID])
|
||||
goto fail;
|
||||
|
||||
sizes[ID] = FileSize;
|
||||
}
|
||||
|
||||
utf_close(utf);
|
||||
utf = NULL;
|
||||
|
||||
|
||||
/* find actual offset */
|
||||
offset = ContentOffset;
|
||||
for (i = 0; i < Files; i++) {
|
||||
uint32_t size = sizes[i];
|
||||
if (i + 1 == target_subsong) {
|
||||
subfile_id = i;
|
||||
subfile_offset = offset;
|
||||
subfile_size = size;
|
||||
break;
|
||||
}
|
||||
|
||||
offset += size;
|
||||
if (Align && (offset % Align))
|
||||
offset += Align - (offset % Align);
|
||||
}
|
||||
|
||||
free(sizes);
|
||||
sizes = NULL;
|
||||
}
|
||||
|
||||
if (!subfile_offset)
|
||||
goto fail;
|
||||
|
||||
//;VGM_LOG("CPK: subfile offset=%lx + %x, id=%i\n", subfile_offset, subfile_size, subfile_id);
|
||||
|
||||
|
||||
if ((read_u32be(subfile_offset,sf) & 0x7f7f7f7f) == 0x48434100) { /* "HCA\0" */
|
||||
type = HCA;
|
||||
extension = "hca";
|
||||
}
|
||||
else if (read_u32be(subfile_offset,sf) == 0x43574156) { /* "CWAV" */
|
||||
type = CWAV;
|
||||
extension = "bcwav";
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, extension);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
switch(type) {
|
||||
case HCA:
|
||||
vgmstream = init_vgmstream_hca(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
case CWAV: /* Metal Gear Solid: Snake Eater 3D (3DS) */
|
||||
vgmstream = init_vgmstream_rwsd(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
|
||||
/* try to load cue names */
|
||||
load_cpk_name(sf, sf_acb, vgmstream, subfile_id);
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
free(sizes);
|
||||
utf_close(utf);
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void load_cpk_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid) {
|
||||
int is_memory = (sf_acb != NULL);
|
||||
|
||||
/* .acb is passed when loading memory .awb inside .acb */
|
||||
if (!is_memory) {
|
||||
/* try parsing TXTM if present */
|
||||
sf_acb = read_filemap_file(sf, 0);
|
||||
|
||||
/* try (name).awb + (name).awb */
|
||||
if (!sf_acb)
|
||||
sf_acb = open_streamfile_by_ext(sf, "acb");
|
||||
|
||||
/* (name)_streamfiles.awb + (name).acb also exist */
|
||||
|
||||
if (!sf_acb)
|
||||
return;
|
||||
|
||||
/* companion .acb probably loaded */
|
||||
load_acb_wave_name(sf_acb, vgmstream, waveid, is_memory);
|
||||
|
||||
close_streamfile(sf_acb);
|
||||
}
|
||||
else {
|
||||
load_acb_wave_name(sf_acb, vgmstream, waveid, is_memory);
|
||||
}
|
||||
}
|
@ -369,15 +369,30 @@ static int utf_query_value(utf_context* utf, int row, const char* column, void*
|
||||
return 1;
|
||||
}
|
||||
|
||||
int utf_query_s8(utf_context* utf, int row, const char* column, int8_t* value) {
|
||||
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT8);
|
||||
}
|
||||
int utf_query_u8(utf_context* utf, int row, const char* column, uint8_t* value) {
|
||||
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT8);
|
||||
}
|
||||
int utf_query_s16(utf_context* utf, int row, const char* column, int16_t* value) {
|
||||
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT16);
|
||||
}
|
||||
int utf_query_u16(utf_context* utf, int row, const char* column, uint16_t* value) {
|
||||
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT16);
|
||||
}
|
||||
int utf_query_s32(utf_context* utf, int row, const char* column, int32_t* value) {
|
||||
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT32);
|
||||
}
|
||||
int utf_query_u32(utf_context* utf, int row, const char* column, uint32_t* value) {
|
||||
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT32);
|
||||
}
|
||||
int utf_query_s64(utf_context* utf, int row, const char* column, int64_t* value) {
|
||||
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_SINT64);
|
||||
}
|
||||
int utf_query_u64(utf_context* utf, int row, const char* column, uint64_t* value) {
|
||||
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_UINT64);
|
||||
}
|
||||
int utf_query_string(utf_context* utf, int row, const char* column, const char** value) {
|
||||
return utf_query_value(utf, row, column, (void*)value, COLUMN_TYPE_STRING);
|
||||
}
|
||||
|
@ -24,9 +24,14 @@ typedef struct utf_context utf_context;
|
||||
utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const char** p_row_name);
|
||||
void utf_close(utf_context* utf);
|
||||
/* query calls */
|
||||
int utf_query_s8(utf_context* utf, int row, const char* column, int8_t* value);
|
||||
int utf_query_u8(utf_context* utf, int row, const char* column, uint8_t* value);
|
||||
int utf_query_s16(utf_context* utf, int row, const char* column, int16_t* value);
|
||||
int utf_query_u16(utf_context* utf, int row, const char* column, uint16_t* value);
|
||||
int utf_query_s32(utf_context* utf, int row, const char* column, int32_t* value);
|
||||
int utf_query_u32(utf_context* utf, int row, const char* column, uint32_t* value);
|
||||
int utf_query_s64(utf_context* utf, int row, const char* column, int64_t* value);
|
||||
int utf_query_u64(utf_context* utf, int row, const char* column, uint64_t* value);
|
||||
int utf_query_string(utf_context* utf, int row, const char* column, const char** value);
|
||||
int utf_query_data(utf_context* utf, int row, const char* column, uint32_t* offset, uint32_t* size);
|
||||
|
||||
|
@ -181,9 +181,10 @@ done:
|
||||
static void bruteforce_hca_key(STREAMFILE* sf, hca_codec_data* hca_data, unsigned long long* out_keycode, uint16_t subkey) {
|
||||
STREAMFILE* sf_keys = NULL;
|
||||
uint8_t* buf = NULL;
|
||||
int best_score = -1;
|
||||
int best_score = 0xFFFFFF, cur_score;
|
||||
off_t keys_size, bytes;
|
||||
int pos;
|
||||
uint64_t old_key = 0;
|
||||
|
||||
|
||||
VGM_LOG("HCA: test keys\n");
|
||||
@ -202,9 +203,12 @@ static void bruteforce_hca_key(STREAMFILE* sf, hca_codec_data* hca_data, unsigne
|
||||
bytes = read_streamfile(buf, 0, keys_size, sf_keys);
|
||||
if (bytes != keys_size) goto done;
|
||||
|
||||
VGM_LOG("HCA: start\n");
|
||||
|
||||
pos = 0;
|
||||
while (pos < keys_size - 4) {
|
||||
uint64_t key;
|
||||
VGM_ASSERT(pos % 0x1000000 == 0, "HCA: pos %x...\n", pos);
|
||||
|
||||
/* keys are usually u32le lower, u32le upper (u64le) but other orders may exist */
|
||||
key = ((uint64_t)get_u32le(buf + pos + 0x00) << 0 ) | ((uint64_t)get_u32le(buf + pos + 0x04) << 32);
|
||||
@ -213,25 +217,32 @@ static void bruteforce_hca_key(STREAMFILE* sf, hca_codec_data* hca_data, unsigne
|
||||
//key = ((uint64_t)get_u32be(buf + pos + 0x00) << 32) | ((uint64_t)get_u32be(buf + pos + 0x04) << 0);
|
||||
//key = ((uint64_t)get_u32le(buf + pos + 0x00) << 0 ) | 0; /* upper bytes not set, ex. P5 */
|
||||
//key = ((uint64_t)get_u32be(buf + pos + 0x00) << 0 ) | 0; /* upper bytes not set, ex. P5 */
|
||||
if (key == 0)
|
||||
continue;
|
||||
|
||||
test_key(hca_data, key, subkey, &best_score, out_keycode);
|
||||
if (best_score == 1)
|
||||
/* observed files have aligned keys, change if needed */
|
||||
pos += 0x04; //pos++;
|
||||
|
||||
if (key == 0 || key == old_key)
|
||||
continue;
|
||||
old_key = key;
|
||||
|
||||
cur_score = 0;
|
||||
test_key(hca_data, key, subkey, &cur_score, out_keycode);
|
||||
if (cur_score == 1)
|
||||
goto done;
|
||||
|
||||
VGM_ASSERT(pos % 0x100000 == 0, "HCA: pos %x...\n", pos);
|
||||
|
||||
/* observed files have aligned keys in the .text section, change if needed */
|
||||
pos += 0x04;
|
||||
//pos++;
|
||||
if (cur_score > 0 && cur_score <= 500) {
|
||||
VGM_LOG("HCA: possible key=%08x%08x (score=%i) at %x\n",
|
||||
(uint32_t)((key >> 32) & 0xFFFFFFFF), (uint32_t)(key & 0xFFFFFFFF), cur_score, pos-0x04);
|
||||
if (best_score > cur_score)
|
||||
best_score = cur_score;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
VGM_ASSERT(best_score > 0, "HCA: best key=%08x%08x (score=%i)\n",
|
||||
(uint32_t)((*out_keycode >> 32) & 0xFFFFFFFF), (uint32_t)(*out_keycode & 0xFFFFFFFF), best_score);
|
||||
VGM_ASSERT(best_score < 0, "HCA: key not found\n");
|
||||
|
||||
|
||||
close_streamfile(sf_keys);
|
||||
free(buf);
|
||||
}
|
||||
|
@ -373,6 +373,9 @@ static const hcakey_info hcakey_list[] = {
|
||||
/* Re:Zero - Lost in Memories (Android) */
|
||||
{1611432018519751642}, // 165CF4E2138F7BDA
|
||||
|
||||
/* D4DJ Groovy Mix (Android) [base files] */
|
||||
{393410674916959300}, // 0575ACECA945A444
|
||||
|
||||
/* Dragalia Lost (iOS/Android) */
|
||||
{2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD
|
||||
|
||||
|
@ -645,6 +645,7 @@ VGMSTREAM* init_vgmstream_opus_nxa(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_opus_opusx(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_opus_prototype(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_opus_opusnx(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_opus_nsopus(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_opus_sqex(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_raw_al(STREAMFILE * streamFile);
|
||||
@ -924,4 +925,7 @@ VGMSTREAM* init_vgmstream_xse_old(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_wady(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_cpk(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_cpk_memory(STREAMFILE* sf, STREAMFILE* sf_acb);
|
||||
|
||||
#endif /*_META_H*/
|
||||
|
@ -3,28 +3,29 @@
|
||||
|
||||
#include "nus3bank_streamfile.h"
|
||||
|
||||
typedef enum { IDSP, IVAG, BNSF, RIFF, OPUS, RIFF_ENC, } nus3bank_codec;
|
||||
typedef enum { IDSP, IVAG, BNSF, RIFF, RIFF_XMA2, OPUS, RIFF_ENC, } nus3bank_codec;
|
||||
|
||||
/* .nus3bank - Namco's newest audio container [Super Smash Bros (Wii U), idolmaster (PS4))] */
|
||||
VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
STREAMFILE *temp_sf = NULL;
|
||||
/* .nus3bank - Namco's newest audio container [Super Smash Bros (Wii U), THE iDOLM@STER 2 (PS3/X360)] */
|
||||
VGMSTREAM* init_vgmstream_nus3bank(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
off_t tone_offset = 0, pack_offset = 0, name_offset = 0, subfile_offset = 0;
|
||||
size_t name_size = 0, subfile_size = 0;
|
||||
nus3bank_codec codec;
|
||||
const char* fake_ext;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
int total_subsongs, target_subsong = sf->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .nub2: early [THE iDOLM@STER 2 (PS3/X360)]
|
||||
* .nus3bank: standard */
|
||||
if (!check_extensions(streamFile, "nub2,nus3bank"))
|
||||
if (!check_extensions(sf, "nub2,nus3bank"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4E555333) /* "NUS3" */
|
||||
if (read_u32be(0x00,sf) != 0x4E555333) /* "NUS3" */
|
||||
goto fail;
|
||||
if (read_32bitBE(0x08,streamFile) != 0x42414E4B) /* "BANK" */
|
||||
if (read_u32be(0x08,sf) != 0x42414E4B) /* "BANK" */
|
||||
goto fail;
|
||||
if (read_32bitBE(0x0c,streamFile) != 0x544F4320) /* "TOC\0" */
|
||||
if (read_u32be(0x0c,sf) != 0x544F4320) /* "TOC\0" */
|
||||
goto fail;
|
||||
|
||||
/* header is always LE, while contained files may use another endianness */
|
||||
@ -32,12 +33,12 @@ VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) {
|
||||
/* parse TOC with all existing chunks and sizes (offsets must be derived) */
|
||||
{
|
||||
int i;
|
||||
off_t offset = 0x14 + read_32bitLE(0x10, streamFile); /* TOC size */
|
||||
size_t chunk_count = read_32bitLE(0x14, streamFile); /* rarely not 7 (ex. SMB U's snd_bgm_CRS12_Simple_Result_Final) */
|
||||
off_t offset = 0x14 + read_u32le(0x10, sf); /* TOC size */
|
||||
size_t chunk_count = read_u32le(0x14, sf); /* rarely not 7 (ex. SMB U's snd_bgm_CRS12_Simple_Result_Final) */
|
||||
|
||||
for (i = 0; i < chunk_count; i++) {
|
||||
uint32_t chunk_id = (uint32_t)read_32bitBE(0x18+(i*0x08)+0x00, streamFile);
|
||||
size_t chunk_size = (size_t)read_32bitLE(0x18+(i*0x08)+0x04, streamFile);
|
||||
uint32_t chunk_id = read_u32be(0x18+(i*0x08)+0x00, sf);
|
||||
size_t chunk_size = read_u32le(0x18+(i*0x08)+0x04, sf);
|
||||
|
||||
switch(chunk_id) {
|
||||
case 0x544F4E45: /* "TONE": stream info */
|
||||
@ -71,7 +72,7 @@ VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) {
|
||||
{
|
||||
int i;
|
||||
uint32_t codec_id = 0;
|
||||
size_t entries = read_32bitLE(tone_offset+0x00, streamFile);
|
||||
size_t entries = read_u32le(tone_offset+0x00, sf);
|
||||
|
||||
/* get actual number of subsongs */
|
||||
total_subsongs = 0;
|
||||
@ -82,8 +83,8 @@ VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) {
|
||||
size_t tone_header_size, stream_name_size, stream_size;
|
||||
uint8_t flags2;
|
||||
|
||||
tone_header_offset = read_32bitLE(tone_offset+0x04+(i*0x08)+0x00, streamFile);
|
||||
tone_header_size = read_32bitLE(tone_offset+0x04+(i*0x08)+0x04, streamFile);
|
||||
tone_header_offset = read_u32le(tone_offset+0x04+(i*0x08)+0x00, sf);
|
||||
tone_header_size = read_u32le(tone_offset+0x04+(i*0x08)+0x04, sf);
|
||||
|
||||
offset = tone_offset + tone_header_offset;
|
||||
//;VGM_LOG("NUS3BANK: tone at %lx, size %x\n", tone_offset + tone_header_offset, tone_header_size);
|
||||
@ -96,7 +97,7 @@ VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) {
|
||||
/* 0x00: type? normally 0x00 and rarely 0x09 */
|
||||
/* 0x04: usually -1, found when tone is not a stream (most flags are off too) */
|
||||
/* 0x06: flags1 */
|
||||
flags2 = read_8bit(offset + 0x07, streamFile);
|
||||
flags2 = read_8bit(offset + 0x07, sf);
|
||||
offset += 0x08;
|
||||
|
||||
/* flags3-6 (early .nub2 and some odd non-stream don't have them) */
|
||||
@ -104,18 +105,18 @@ VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) {
|
||||
offset += 0x04;
|
||||
}
|
||||
|
||||
stream_name_size = read_8bit(offset + 0x00, streamFile); /* includes null */
|
||||
stream_name_size = read_8bit(offset + 0x00, sf); /* includes null */
|
||||
stream_name_offset = offset + 0x01;
|
||||
offset += align_size_to_block(0x01 + stream_name_size, 0x04); /* padded if needed */
|
||||
|
||||
/* 0x00: subtype? should be 0 */
|
||||
if (read_32bitLE(offset + 0x04, streamFile) != 0x08) { /* flag? */
|
||||
if (read_u32le(offset + 0x04, sf) != 0x08) { /* flag? */
|
||||
//;VGM_LOG("NUS3BANK: bad tone type at %lx, size %x\n", tone_offset + tone_header_offset, tone_header_size);
|
||||
continue;
|
||||
}
|
||||
|
||||
stream_offset = read_32bitLE(offset + 0x08, streamFile) + pack_offset;
|
||||
stream_size = read_32bitLE(offset + 0x0c, streamFile);
|
||||
stream_offset = read_u32le(offset + 0x08, sf) + pack_offset;
|
||||
stream_size = read_u32le(offset + 0x0c, sf);
|
||||
//;VGM_LOG("NUS3BANK: so=%lx, ss=%x\n", stream_offset, stream_size);
|
||||
|
||||
/* Beyond are a bunch of sub-chunks of unknown size with floats and stuff, that seemingly
|
||||
@ -148,17 +149,25 @@ VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) {
|
||||
}
|
||||
|
||||
//todo improve, codec may be in one of the tone sub-chunks (or other chunk? one bank seems to use one codec)
|
||||
codec_id = read_32bitBE(subfile_offset, streamFile);
|
||||
codec_id = read_u32be(subfile_offset + 0x00, sf);
|
||||
switch(codec_id) {
|
||||
case 0x49445350: /* "IDSP" [Super Smash Bros. for 3DS (3DS)] */
|
||||
codec = IDSP;
|
||||
fake_ext = "idsp";
|
||||
break;
|
||||
|
||||
case 0x52494646: /* "RIFF" [THE iDOLM@STER 2 (PS3), Mario Kart Arcade GP DX (PC), idolm@ster: Platinum Stars (PS4)] */
|
||||
codec = RIFF;
|
||||
fake_ext = "wav"; //TODO: works but should have better detection
|
||||
case 0x52494646: { /* "RIFF" [THE iDOLM@STER 2 (PS3), Mario Kart Arcade GP DX (PC), idolm@ster: Platinum Stars (PS4)] */
|
||||
uint16_t format = read_u16le(subfile_offset + 0x14, sf);
|
||||
if (format == 0x0166) { /* Tekken Tag Tournament 2 (X360) */
|
||||
codec = RIFF_XMA2;
|
||||
fake_ext = "xma";
|
||||
}
|
||||
else {
|
||||
codec = RIFF;
|
||||
fake_ext = "wav"; //TODO: works but should have better detection
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x4F505553: /* "OPUS" [Taiko no Tatsujin (Switch)] */
|
||||
codec = OPUS;
|
||||
@ -186,9 +195,9 @@ VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) {
|
||||
}
|
||||
}
|
||||
|
||||
//;VGM_LOG("NUS3BANK: subfile=%lx, size=%x\n", subfile_offset, subfile_size);
|
||||
//;VGM_LOG("NUS3BANK: subfile=%lx, size=%x, %s\n", subfile_offset, subfile_size, fake_ext);
|
||||
|
||||
temp_sf = setup_subfile_streamfile(streamFile, subfile_offset, subfile_size, fake_ext);
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, fake_ext);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
/* init the VGMSTREAM */
|
||||
@ -218,6 +227,11 @@ VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) {
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
|
||||
case RIFF_XMA2:
|
||||
vgmstream = init_vgmstream_xma(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
|
||||
case RIFF_ENC:
|
||||
vgmstream = init_vgmstream_nus3bank_encrypted(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
@ -229,7 +243,7 @@ VGMSTREAM * init_vgmstream_nus3bank(STREAMFILE *streamFile) {
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
if (name_offset)
|
||||
read_string(vgmstream->stream_name,name_size, name_offset,streamFile);
|
||||
read_string(vgmstream->stream_name, name_size, name_offset, sf);
|
||||
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
@ -242,9 +256,9 @@ fail:
|
||||
}
|
||||
|
||||
/* encrypted RIFF from the above, in case kids try to extract and play single files */
|
||||
VGMSTREAM* init_vgmstream_nus3bank_encrypted(STREAMFILE *sf) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
STREAMFILE *temp_sf = NULL;
|
||||
VGMSTREAM* init_vgmstream_nus3bank_encrypted(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
|
||||
|
||||
/* checks */
|
||||
|
@ -427,6 +427,25 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Edelweiss variation [Sakuna: Of Rice and Ruin (Switch)] */
|
||||
VGMSTREAM* init_vgmstream_opus_nsopus(STREAMFILE* sf) {
|
||||
off_t offset = 0;
|
||||
int num_samples = 0, loop_start = 0, loop_end = 0;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "nsopus"))
|
||||
goto fail;
|
||||
if (read_u32be(0x00, sf) != 0x45574E4F) /* "EWNO" */
|
||||
goto fail;
|
||||
|
||||
offset = 0x08;
|
||||
num_samples = 0; //read_32bitLE(0x08, sf); /* samples without encoder delay? (lower than count) */
|
||||
|
||||
return init_vgmstream_opus(sf, 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* sf) {
|
||||
off_t offset = 0;
|
||||
|
@ -417,7 +417,7 @@ static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_done)
|
||||
start = 0;
|
||||
}
|
||||
|
||||
memset(buf + (start * channels), 0, (samples_done - start) * sizeof(sample_t) * channels);
|
||||
memset(buf + (start * channels), 0, (samples_done - start) * channels * sizeof(sample_t));
|
||||
return samples_done;
|
||||
}
|
||||
|
||||
@ -455,7 +455,8 @@ int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
|
||||
/* end padding (done before to avoid decoding if possible, samples_to_do becomes 0) */
|
||||
if (!vgmstream->config.play_forever /* && ps->pad_end_left */
|
||||
&& ps->play_position + samples_done >= ps->pad_end_start) {
|
||||
&& ps->play_position + samples_done >= ps->pad_end_start
|
||||
&& samples_to_do) {
|
||||
done = render_pad_end(vgmstream, tmpbuf, samples_to_do);
|
||||
samples_done += done;
|
||||
samples_to_do -= done;
|
||||
|
@ -510,6 +510,8 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
|
||||
init_vgmstream_dsp_sqex,
|
||||
init_vgmstream_dsp_wiivoice,
|
||||
init_vgmstream_xws,
|
||||
init_vgmstream_cpk,
|
||||
init_vgmstream_opus_nsopus,
|
||||
|
||||
/* 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 */
|
||||
|
Loading…
Reference in New Issue
Block a user