Merge pull request #758 from bnnm/cpk-etc

cpk etc
This commit is contained in:
bnnm 2020-11-16 00:19:07 +01:00 committed by GitHub
commit d05fa3a771
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 5782 additions and 5387 deletions

181
README.md
View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -345,6 +345,7 @@ static const char* extension_list[] = {
"nop",
"nps",
"npsf", //fake extension/header id for .nps (in bigfiles)
"nsopus",
"nub",
"nub2",
"nus3audio",

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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;
}

View File

@ -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

View File

@ -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 */

View File

@ -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) {

View File

@ -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
View 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);
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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*/

View File

@ -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 */

View File

@ -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;

View File

@ -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;

View File

@ -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 */