mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-02-20 20:41:08 +01:00
commit
46467c974f
51
README.md
51
README.md
@ -112,12 +112,12 @@ automatically. You need to manually refresh it by selecting songs and doing
|
||||
**shift + right click > Tagging > Reload info from file(s)**.
|
||||
|
||||
### Audacious plugin
|
||||
*Installation*: needs to be manually built. Instructions can be found in the BUILD
|
||||
document in vgmstream's source code.
|
||||
*Installation*: needs to be manually built. Instructions can be found in doc/BUILD.md
|
||||
document in vgmstream's source code (can be done with CMake or autotools).
|
||||
|
||||
### vgmstream123
|
||||
*Installation*: needs to be manually built. Instructions can be found in the BUILD
|
||||
document in vgmstream's source code.
|
||||
*Installation*: needs to be manually built. Instructions can be found in doc/BUILD.md
|
||||
document in vgmstream's source code (can be done with CMake or autotools).
|
||||
|
||||
Usage: `vgmstream123 [options] INFILE ...`
|
||||
|
||||
@ -156,7 +156,7 @@ 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`.
|
||||
subsongs to `txtp_maker.py` (it has CLI options to control output too).
|
||||
|
||||
### Renamed files
|
||||
A few extensions that vgmstream supports clash with common ones. Since players
|
||||
@ -189,8 +189,8 @@ internal loop info, or apply subtle fixes, but is also limited in some ways
|
||||
may work as a last resort to make a file playable.
|
||||
|
||||
Some plugins have options that allow any extension (common or unknown) to be
|
||||
played, making renaming unnecessary (may need to adjust plugin priority in
|
||||
player's options).
|
||||
played, making renaming unnecessary. You may need to adjust plugin priority in
|
||||
player's options first.
|
||||
|
||||
Also be aware that some plugins can tell the player they handle some extension,
|
||||
then not actually play it. This makes the file unplayable as vgmstream doesn't
|
||||
@ -223,7 +223,7 @@ on the internet.
|
||||
|
||||
### Companion files
|
||||
Some formats have companion files with external info, that should be left together:
|
||||
- `.mus`: playlist for `.acm`
|
||||
- `.mus`: playlist with `.acm`
|
||||
- `.ogg.sli` or `.sli`: loop info for `.ogg`
|
||||
- `.ogg.sfl` : loop info for `.ogg`
|
||||
- `.opus.sli`: loop info for `.opus`
|
||||
@ -250,12 +250,12 @@ Similarly some formats split header+body data in separate files, examples:
|
||||
- `.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).
|
||||
bigger file (body), save a few formats where the smaller (header) file is opened
|
||||
instead for technical reasons (mainly some bank formats).
|
||||
|
||||
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`).
|
||||
which are hardcoded instead of being listed in the header 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.
|
||||
|
||||
@ -304,7 +304,7 @@ a companion file:
|
||||
- `.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)
|
||||
- `.fsb`: `.fsbkey` (decryption key in hex, usually between 8-32 bytes)
|
||||
- `.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"
|
||||
@ -396,8 +396,10 @@ a file named `song.adx#C1,2.txtp` to play only channels 1 and 2 from `song.adx`.
|
||||
Some of vgmstream's plugins support simple read-only tagging via external files.
|
||||
|
||||
Tags are loaded from a text/M3U-like file named *!tags.m3u* in the song folder.
|
||||
You don't have to load your songs with that M3U though (but you can, for pre-made
|
||||
ordering), the file itself just 'looks' like an M3U.
|
||||
You don't have to load your songs with this M3U though, but you can (for pre-made
|
||||
order). The format is meant to be both a quick playlist and tags, but the tagfile
|
||||
itself just 'looks' like an M3U. you can load files manually or using other playlists
|
||||
and still get tags.
|
||||
|
||||
Format is:
|
||||
```
|
||||
@ -416,14 +418,16 @@ or uppercase, separated by one or multiple spaces. Repeated tags overwrite previ
|
||||
(ex.- may define *@COMPOSER* multiple times for "sections"). It only reads up to
|
||||
current *filename* though, so any *@TAG* below would be ignored.
|
||||
|
||||
Playlist title formatting should follow player's config. ASCII or UTF-8 tags work.
|
||||
|
||||
*GLOBAL_COMMAND*s currently can be:
|
||||
- *AUTOTRACK*: sets *%TRACK* tag automatically (1..N as files are encountered
|
||||
in the tag file).
|
||||
- *AUTOALBUM*: sets *%ALBUM* tag automatically using the containing dir as album.
|
||||
- *EXACTMATCH*: disables matching .txtp with regular files (explained below).
|
||||
|
||||
Playlist title formatting (how tags are shown) should follow player's config, as
|
||||
vgmstream simply passes tags to the player. It's better to name the file lowercase
|
||||
`!tags.m3u` rather than `!Tags.m3u` (Windows accepts both but Linux is case sensitive).
|
||||
|
||||
Note that with global tags you don't need to put all files inside. This would be
|
||||
a perfectly valid *!tags.m3u*:
|
||||
```
|
||||
@ -431,6 +435,21 @@ a perfectly valid *!tags.m3u*:
|
||||
# @ARTIST Various Artists
|
||||
```
|
||||
|
||||
### Non-English filenames and tags
|
||||
Tags and filenames using extended characters (like Japanese) should work, as long
|
||||
as `!tags.m3u` is saved as *"UTF-8 with BOM"* (UTF-8 is a way to define non-English
|
||||
characters, and BOM is a helper "byte-order" mark). Windows' *notepad* creates files
|
||||
*"with BOM"* when selecting UTF-8 encoding in *save as* dialog, or you may use other
|
||||
programs like *notepad++.exe* to convert them.
|
||||
|
||||
More exactly, vgmstream matches filenames and reads tags assuming they are in UTF-8,
|
||||
while foobar/winamp can only read UTF-8 Japanese/extended filenames in a `.m3u` if file
|
||||
is saved *with BOM* (opening files manually or with a `playlist.m3u8` won't need BOM).
|
||||
|
||||
Other players may not need BOM (or CRLF), but for consistency use them when dealing
|
||||
with non-ASCII names and tags.
|
||||
|
||||
|
||||
### Tags with spaces
|
||||
Some players like foobar accept tags with spaces. To use them surround the tag
|
||||
with both characters.
|
||||
|
@ -548,6 +548,8 @@ typedef struct {
|
||||
/* frame table */
|
||||
off_t table_offset;
|
||||
int table_count;
|
||||
/* fixed frames */
|
||||
uint16_t frame_size;
|
||||
} opus_config;
|
||||
|
||||
ffmpeg_codec_data* init_ffmpeg_switch_opus_config(STREAMFILE* sf, off_t start_offset, size_t data_size, opus_config* cfg);
|
||||
@ -557,6 +559,7 @@ ffmpeg_codec_data* init_ffmpeg_ea_opus(STREAMFILE* sf, off_t start_offset, size_
|
||||
ffmpeg_codec_data* init_ffmpeg_x_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip);
|
||||
ffmpeg_codec_data* init_ffmpeg_fsb_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
|
||||
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg);
|
||||
ffmpeg_codec_data* init_ffmpeg_fixed_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg);
|
||||
|
||||
size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE* sf);
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
* https://github.com/hcs64/ww2ogg
|
||||
*/
|
||||
|
||||
typedef enum { OPUS_SWITCH, OPUS_UE4_v1, OPUS_UE4_v2, OPUS_EA, OPUS_X, OPUS_FSB, OPUS_WWISE } opus_type_t;
|
||||
typedef enum { OPUS_SWITCH, OPUS_UE4_v1, OPUS_UE4_v2, OPUS_EA, OPUS_X, OPUS_FSB, OPUS_WWISE, OPUS_FIXED } opus_type_t;
|
||||
|
||||
static size_t make_oggs_first(uint8_t *buf, int buf_size, opus_config *cfg);
|
||||
static size_t make_oggs_page(uint8_t *buf, int buf_size, size_t data_size, int page_sequence, int granule);
|
||||
@ -36,6 +36,9 @@ typedef struct {
|
||||
int table_count;
|
||||
uint16_t* frame_table;
|
||||
|
||||
/* fixed frame size for variations that use this */
|
||||
uint16_t frame_size;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* offset that corresponds to physical_offset */
|
||||
off_t physical_offset; /* actual file offset */
|
||||
@ -130,6 +133,10 @@ static size_t opus_io_read(STREAMFILE* sf, uint8_t *dest, off_t offset, size_t l
|
||||
data_size = get_table_frame_size(data, data->sequence - 2);
|
||||
skip_size = 0;
|
||||
break;
|
||||
case OPUS_FIXED:
|
||||
data_size = data->frame_size;
|
||||
skip_size = 0;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@ -231,6 +238,10 @@ static size_t opus_io_size(STREAMFILE* sf, opus_io_data* data) {
|
||||
data_size = get_table_frame_size(data, packet);
|
||||
skip_size = 0x00;
|
||||
break;
|
||||
case OPUS_FIXED:
|
||||
data_size = data->frame_size;
|
||||
skip_size = 0;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@ -295,7 +306,7 @@ static void opus_io_close(STREAMFILE* sf, opus_io_data* data) {
|
||||
|
||||
|
||||
/* Prepares custom IO for custom Opus, that is converted to Ogg Opus on the fly */
|
||||
static STREAMFILE* setup_opus_streamfile(STREAMFILE* sf, opus_config *cfg, off_t stream_offset, size_t stream_size, opus_type_t type) {
|
||||
static STREAMFILE* setup_opus_streamfile(STREAMFILE* sf, opus_config* cfg, off_t stream_offset, size_t stream_size, opus_type_t type) {
|
||||
STREAMFILE* new_sf = NULL;
|
||||
opus_io_data io_data = {0};
|
||||
|
||||
@ -308,6 +319,7 @@ static STREAMFILE* setup_opus_streamfile(STREAMFILE* sf, opus_config *cfg, off_t
|
||||
io_data.physical_offset = stream_offset;
|
||||
io_data.table_offset = cfg->table_offset;
|
||||
io_data.table_count = cfg->table_count;
|
||||
io_data.frame_size = cfg->frame_size;
|
||||
|
||||
io_data.head_size = make_oggs_first(io_data.head_buffer, sizeof(io_data.head_buffer), cfg);
|
||||
if (!io_data.head_size) goto fail;
|
||||
@ -612,12 +624,16 @@ static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFI
|
||||
skip_size = 0x02;
|
||||
break;
|
||||
|
||||
#if 0 // needs data* for frame table, but num_samples should exist on header
|
||||
#if 0 //needs data*, num_samples should exist on header
|
||||
case OPUS_X:
|
||||
case OPUS_WWISE:
|
||||
data_size = get_table_frame_size(data, packet);
|
||||
skip_size = 0x00;
|
||||
break;
|
||||
case OPUS_FIXED:
|
||||
data_size = data->frame_size;
|
||||
skip_size = 0;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
return 0;
|
||||
@ -661,6 +677,11 @@ static size_t custom_opus_get_encoder_delay(off_t offset, STREAMFILE* sf, opus_t
|
||||
case OPUS_WWISE:
|
||||
skip_size = 0x00;
|
||||
break;
|
||||
#if 0 //should exist on header
|
||||
case OPUS_FIXED:
|
||||
skip_size = 0x00;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@ -756,6 +777,9 @@ ffmpeg_codec_data* init_ffmpeg_fsb_opus(STREAMFILE* sf, off_t start_offset, size
|
||||
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg) {
|
||||
return init_ffmpeg_custom_opus_config(sf, data_offset, data_size, cfg, OPUS_WWISE);
|
||||
}
|
||||
ffmpeg_codec_data* init_ffmpeg_fixed_opus(STREAMFILE* sf, off_t data_offset, size_t data_size, opus_config* cfg) {
|
||||
return init_ffmpeg_custom_opus_config(sf, data_offset, data_size, cfg, OPUS_FIXED);
|
||||
}
|
||||
|
||||
static opus_type_t get_ue4opus_version(STREAMFILE* sf, off_t offset) {
|
||||
int read_samples, calc_samples;
|
||||
|
@ -97,23 +97,23 @@ static int ealayer3_is_empty_frame_v2p(STREAMFILE* sf, off_t offset);
|
||||
/* **************************************************************************** */
|
||||
|
||||
/* init codec from an EALayer3 frame */
|
||||
int mpeg_custom_setup_init_ealayer3(STREAMFILE* streamfile, off_t start_offset, mpeg_codec_data* data, coding_t *coding_type) {
|
||||
int mpeg_custom_setup_init_ealayer3(STREAMFILE* sf, off_t start_offset, mpeg_codec_data* data, coding_t *coding_type) {
|
||||
int ok;
|
||||
ealayer3_buffer_t ib = {0};
|
||||
ealayer3_frame_t eaf;
|
||||
|
||||
|
||||
//;VGM_LOG("init at %lx\n", start_offset);
|
||||
//;VGM_LOG("init at %lx, %x\n", start_offset, read_u32be(start_offset, sf));
|
||||
/* get first frame for info */
|
||||
{
|
||||
ib.sf = streamfile;
|
||||
ib.sf = sf;
|
||||
ib.offset = start_offset;
|
||||
ib.is.buf = ib.buf;
|
||||
|
||||
ok = ealayer3_parse_frame(data, -1, &ib, &eaf);
|
||||
if (!ok) goto fail;
|
||||
}
|
||||
;VGM_ASSERT(!eaf.mpeg1, "EAL3: mpeg2 found at 0x%lx\n", start_offset); /* rare [FIFA 08 (PS3) abk] */
|
||||
VGM_ASSERT(!eaf.mpeg1, "EAL3: mpeg2 found at 0x%lx\n", start_offset); /* rare [FIFA 08 (PS3) abk] */
|
||||
|
||||
*coding_type = coding_MPEG_ealayer3;
|
||||
data->channels_per_frame = eaf.channels;
|
||||
@ -385,11 +385,16 @@ static int ealayer3_parse_frame_v2(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf)
|
||||
ok = ealayer3_parse_frame_common(ib, eaf);
|
||||
if (!ok) goto fail;
|
||||
}
|
||||
else {
|
||||
/* rarely frames contain PCM data only [FIFA 2014 World Cup Brazil (PS3)] */
|
||||
eaf->channels = eaf->v2_stereo_flag + 1;
|
||||
}
|
||||
|
||||
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_common_size == 0, "EA EAL3: v2 empty frame\n"); /* seen in V2S */
|
||||
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_offset_samples > 0, "EA EAL3: v2_offset_mode=%x with value=0x%x\n", eaf->v2_offset_mode, eaf->v2_offset_samples);
|
||||
//VGM_ASSERT(eaf->v2_pcm_samples > 0, "EA EAL3: v2_pcm_samples 0x%x\n", eaf->v2_pcm_samples);
|
||||
|
||||
eaf->pcm_size = (2*eaf->v2_pcm_samples * eaf->channels);
|
||||
eaf->pcm_size = (eaf->v2_pcm_samples * sizeof(int16_t) * eaf->channels);
|
||||
|
||||
eaf->eaframe_size = eaf->pre_size + eaf->common_size + eaf->pcm_size;
|
||||
|
||||
@ -408,14 +413,14 @@ fail:
|
||||
/* parses an EALayer3 frame (common part) */
|
||||
static int ealayer3_parse_frame_common(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf) {
|
||||
/* index tables */
|
||||
static const int versions[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 };
|
||||
static const int sample_rates[4][4] = { /* [version_index][sample rate index] */
|
||||
static const int version_table[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1, /* MPEG 2 */ 2, /* MPEG 1 */ 1 };
|
||||
static const int sample_rate_table[4][4] = { /* [version_index][sample rate index] */
|
||||
{ 11025, 12000, 8000, -1}, /* MPEG2.5 */
|
||||
{ -1, -1, -1, -1}, /* reserved */
|
||||
{ 22050, 24000, 16000, -1}, /* MPEG2 */
|
||||
{ 44100, 48000, 32000, -1}, /* MPEG1 */
|
||||
};
|
||||
static const int channels[4] = { 2,2,2, 1 }; /* [channel_mode] */
|
||||
static const int channel_table[4] = { 2,2,2, 1 }; /* [channel_mode] */
|
||||
|
||||
bitstream_t* is = &ib->is;
|
||||
off_t start_b_off = is->b_off;
|
||||
@ -441,9 +446,9 @@ static int ealayer3_parse_frame_common(ealayer3_buffer_t* ib, ealayer3_frame_t*
|
||||
|
||||
|
||||
/* derived */
|
||||
eaf->version = versions[eaf->version_index];
|
||||
eaf->channels = channels[eaf->channel_mode];
|
||||
eaf->sample_rate = sample_rates[eaf->version_index][eaf->sample_rate_index];
|
||||
eaf->version = version_table[eaf->version_index];
|
||||
eaf->channels = channel_table[eaf->channel_mode];
|
||||
eaf->sample_rate = sample_rate_table[eaf->version_index][eaf->sample_rate_index];
|
||||
eaf->mpeg1 = (eaf->version == 1);
|
||||
|
||||
if (eaf->version == -1 || eaf->sample_rate == -1) {
|
||||
|
@ -221,6 +221,7 @@ static const char* extension_list[] = {
|
||||
"idwav",
|
||||
"idx",
|
||||
"idxma",
|
||||
"ifs",
|
||||
"ikm",
|
||||
"ild",
|
||||
"ilv", //txth/reserved [Star Wars Episode III (PS2)]
|
||||
@ -341,7 +342,8 @@ static const char* extension_list[] = {
|
||||
|
||||
"n64",
|
||||
"naac",
|
||||
"ndp",
|
||||
"nds",
|
||||
"ndp", //fake extension/header id for .nds
|
||||
"ngca",
|
||||
"nlsd",
|
||||
"nop",
|
||||
@ -1321,6 +1323,8 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_DSP_SQEX, "Square Enix DSP header"},
|
||||
{meta_DSP_WIIVOICE, "Koei Tecmo WiiVoice header"},
|
||||
{meta_SBK, "Team17 SBK header"},
|
||||
{meta_DSP_WIIADPCM, "Exient WIIADPCM header"},
|
||||
{meta_DSP_CWAC, "CRI CWAC header"},
|
||||
};
|
||||
|
||||
void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) {
|
||||
|
@ -1282,10 +1282,14 @@
|
||||
RelativePath=".\meta\ps2_iab.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\ikm.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\ifs.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\ikm.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\ps2_ild.c"
|
||||
>
|
||||
|
@ -441,6 +441,7 @@
|
||||
<ClCompile Include="meta\ps2_gbts.c" />
|
||||
<ClCompile Include="meta\ps2_gcm.c" />
|
||||
<ClCompile Include="meta\ps2_hgc1.c" />
|
||||
<ClCompile Include="meta\ifs.c" />
|
||||
<ClCompile Include="meta\ikm.c" />
|
||||
<ClCompile Include="meta\ps2_ild.c" />
|
||||
<ClCompile Include="meta\raw_int.c" />
|
||||
|
@ -826,6 +826,9 @@
|
||||
<ClCompile Include="meta\ps2_hgc1.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\ifs.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\ikm.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP } awb_type;
|
||||
typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP, CWAC } awb_type;
|
||||
|
||||
static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid);
|
||||
|
||||
@ -116,6 +116,10 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
|
||||
type = DSP;
|
||||
extension = "dsp";
|
||||
}
|
||||
else if (is_id32be(subfile_offset,sf, "CWAC")) { /* type 13 again */
|
||||
type = CWAC;
|
||||
extension = "dsp";
|
||||
}
|
||||
else {
|
||||
VGM_LOG("AWB: unknown codec\n");
|
||||
goto fail;
|
||||
@ -150,6 +154,10 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
|
||||
vgmstream = init_vgmstream_ngc_dsp_std(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
case CWAC: /* Mario & Sonic at the Rio 2016 Olympic Games (WiiU) */
|
||||
vgmstream = init_vgmstream_dsp_cwac(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
272
src/meta/awc.c
272
src/meta/awc.c
@ -19,12 +19,14 @@ typedef struct {
|
||||
|
||||
off_t stream_offset;
|
||||
size_t stream_size;
|
||||
off_t vorbis_offset;
|
||||
off_t vorbis_offset[VGMSTREAM_MAX_CHANNELS];
|
||||
|
||||
} awc_header;
|
||||
|
||||
static int parse_awc_header(STREAMFILE* sf, awc_header* awc);
|
||||
|
||||
static layered_layout_data* build_layered_awc(STREAMFILE* sf, awc_header* awc);
|
||||
|
||||
|
||||
/* AWC - from RAGE (Rockstar Advanced Game Engine) audio [Red Dead Redemption, Max Payne 3, GTA5 (multi)] */
|
||||
VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
|
||||
@ -154,20 +156,26 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef VGM_USE_MPEG
|
||||
#ifdef VGM_USE_VORBIS
|
||||
case 0x08: { /* Vorbis (PC) [Red Dead Redemption 2 (PC)] */
|
||||
vorbis_custom_config cfg = {0};
|
||||
if (awc.is_music) {
|
||||
vgmstream->layout_data = build_layered_awc(sf, &awc);
|
||||
if (!vgmstream->layout_data) goto fail;
|
||||
vgmstream->layout_type = layout_layered;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
}
|
||||
else {
|
||||
vorbis_custom_config cfg = {0};
|
||||
|
||||
if (awc.is_music) goto fail; /* needs blocks */
|
||||
cfg.channels = awc.channels;
|
||||
cfg.sample_rate = awc.sample_rate;
|
||||
cfg.header_offset = awc.vorbis_offset[0];
|
||||
|
||||
cfg.channels = awc.channels;
|
||||
cfg.sample_rate = awc.sample_rate;
|
||||
cfg.header_offset = awc.vorbis_offset;
|
||||
|
||||
vgmstream->codec_data = init_vorbis_custom(sf, awc.stream_offset, VORBIS_AWC, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->coding_type = coding_VORBIS_custom;
|
||||
vgmstream->codec_data = init_vorbis_custom(sf, awc.stream_offset, VORBIS_AWC, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->coding_type = coding_VORBIS_custom;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
@ -195,7 +203,7 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
||||
uint16_t (*read_u16)(off_t,STREAMFILE*) = NULL;
|
||||
int i, ch, entries;
|
||||
uint32_t flags, info_header, tag_count = 0, tags_skip = 0;
|
||||
off_t off;
|
||||
off_t offset;
|
||||
int target_subsong = sf->stream_index;
|
||||
|
||||
|
||||
@ -220,7 +228,7 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
||||
entries = read_u32(0x08,sf);
|
||||
//header_size = read_u32(0x0c,sf); /* after to stream id/tags, not including chunks */
|
||||
|
||||
off = 0x10;
|
||||
offset = 0x10;
|
||||
|
||||
if ((flags & 0xFF00FFFF) != 0xFF000001 || (flags & 0x00F00000)) {
|
||||
VGM_LOG("AWC: unknown flags 0x%08x\n", flags);
|
||||
@ -228,7 +236,7 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
||||
}
|
||||
|
||||
if (flags & 0x00010000) /* some kind of mini offset table */
|
||||
off += 0x2 * entries;
|
||||
offset += 0x2 * entries;
|
||||
//if (flags % 0x00020000) /* seems to indicate chunks are not ordered (ie. header may go after data) */
|
||||
// ...
|
||||
//if (flags % 0x00040000) /* music/multichannel flag? (GTA5, not seen in RDR) */
|
||||
@ -244,7 +252,7 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
||||
/* Music when the first id is 0 (base/fake entry with info for all channels), sfx pack otherwise.
|
||||
* sfx = N single streams, music = N-1 interleaved mono channels (even for MP3/XMA).
|
||||
* Music seems layered (N-1/2 stereo pairs), maybe set with events? */
|
||||
awc->is_music = (read_u32(off + 0x00,sf) & 0x1FFFFFFF) == 0x00000000;
|
||||
awc->is_music = (read_u32(offset + 0x00,sf) & 0x1FFFFFFF) == 0x00000000;
|
||||
if (awc->is_music) { /* all streams except id 0 is a channel */
|
||||
awc->total_subsongs = 1;
|
||||
target_subsong = 1; /* we only need id 0, though channels may have its own tags/chunks */
|
||||
@ -258,46 +266,47 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
||||
|
||||
/* get stream base info */
|
||||
for (i = 0; i < entries; i++) {
|
||||
info_header = read_u32(off + 0x04*i, sf);
|
||||
info_header = read_u32(offset + 0x04*i, sf);
|
||||
tag_count = (info_header >> 29) & 0x7; /* 3b */
|
||||
//id = (info_header >> 0) & 0x1FFFFFFF; /* 29b */
|
||||
if (target_subsong-1 == i)
|
||||
break;
|
||||
tags_skip += tag_count; /* tags to skip to reach target's tags, in the next header */
|
||||
}
|
||||
off += 0x04*entries;
|
||||
off += 0x08*tags_skip;
|
||||
offset += 0x04*entries;
|
||||
offset += 0x08*tags_skip;
|
||||
|
||||
/* get stream tags */
|
||||
for (i = 0; i < tag_count; i++) {
|
||||
uint64_t tag_header;
|
||||
uint8_t tag;
|
||||
size_t size;
|
||||
off_t offset;
|
||||
uint8_t tag_type;
|
||||
size_t tag_size;
|
||||
off_t tag_offset;
|
||||
|
||||
tag_header = read_u64(off + 0x08*i,sf);
|
||||
tag = (uint8_t)((tag_header >> 56) & 0xFF); /* 8b */
|
||||
size = (size_t)((tag_header >> 28) & 0x0FFFFFFF); /* 28b */
|
||||
offset = (off_t)((tag_header >> 0) & 0x0FFFFFFF); /* 28b */
|
||||
//;VGM_LOG("AWC: tag%i/%i at %lx: t=%x, o=%lx, s=%x\n", i, tag_count, off + 0x08*i, tag, offset, size);
|
||||
tag_header = read_u64(offset + 0x08*i,sf);
|
||||
tag_type = (uint8_t)((tag_header >> 56) & 0xFF); /* 8b */
|
||||
tag_size = (size_t)((tag_header >> 28) & 0x0FFFFFFF); /* 28b */
|
||||
tag_offset = (off_t)((tag_header >> 0) & 0x0FFFFFFF); /* 28b */
|
||||
;VGM_LOG("AWC: tag%i/%i at %lx: t=%x, o=%lx, s=%x\n", i, tag_count, offset + 0x08*i, tag_type, tag_offset, tag_size);
|
||||
|
||||
/* Tags are apparently part of a hash derived from a word ("data", "format", etc).
|
||||
* If music + 1ch, the header and data chunks can repeat for no reason (sometimes not even pointed). */
|
||||
switch(tag) {
|
||||
switch(tag_type) {
|
||||
case 0x55: /* data */
|
||||
awc->stream_offset = offset;
|
||||
awc->stream_size = size;
|
||||
awc->stream_offset = tag_offset;
|
||||
awc->stream_size = tag_size;
|
||||
break;
|
||||
|
||||
case 0x48: /* music header */
|
||||
|
||||
if (!awc->is_music) {
|
||||
VGM_LOG("AWC: music header found in sfx\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* 0x00(32): unknown (some count?) */
|
||||
awc->block_chunk = read_u32(offset + 0x04,sf);
|
||||
awc->channels = read_u32(offset + 0x08,sf);
|
||||
awc->block_chunk = read_u32(tag_offset + 0x04,sf);
|
||||
awc->channels = read_u32(tag_offset + 0x08,sf);
|
||||
|
||||
if (awc->channels != entries - 1) { /* not counting id-0 */
|
||||
VGM_LOG("AWC: number of music channels doesn't match entries\n");
|
||||
@ -307,10 +316,10 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
||||
for (ch = 0; ch < awc->channels; ch++) {
|
||||
int num_samples, sample_rate, codec;
|
||||
/* 0x00): stream id (not always in the header entries order) */
|
||||
num_samples = read_u32(offset + 0x0c + 0x10*ch + 0x04,sf);
|
||||
num_samples = read_u32(tag_offset + 0x0c + 0x10*ch + 0x04,sf);
|
||||
/* 0x08: headroom */
|
||||
sample_rate = read_u16(offset + 0x0c + 0x10*ch + 0x0a,sf);
|
||||
codec = read_u8(offset + 0x0c + 0x10*ch + 0x0c,sf);
|
||||
sample_rate = read_u16(tag_offset + 0x0c + 0x10*ch + 0x0a,sf);
|
||||
codec = read_u8(tag_offset + 0x0c + 0x10*ch + 0x0c,sf);
|
||||
/* 0x0d(8): round size? */
|
||||
/* 0x0e: unknown (zero/-1) */
|
||||
|
||||
@ -334,11 +343,6 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
||||
awc->codec = codec;
|
||||
}
|
||||
|
||||
if (awc->codec == 0x08) {
|
||||
/* one vorbis header per channel, pasted together */
|
||||
awc->vorbis_offset = offset + size;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 0xFA: /* sfx header */
|
||||
@ -347,12 +351,12 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
awc->num_samples = read_u32(offset + 0x00,sf);
|
||||
awc->num_samples = read_u32(tag_offset + 0x00,sf);
|
||||
/* 0x04: -1? */
|
||||
awc->sample_rate = read_u16(offset + 0x08,sf);
|
||||
awc->sample_rate = read_u16(tag_offset + 0x08,sf);
|
||||
/* 0x0a: unknown x4 */
|
||||
/* 0x12: null? */
|
||||
awc->codec = read_u8(offset + 0x13, sf);
|
||||
awc->codec = read_u8(tag_offset + 0x13, sf);
|
||||
awc->channels = 1;
|
||||
break;
|
||||
|
||||
@ -362,17 +366,17 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
awc->num_samples = read_u32(offset + 0x00,sf);
|
||||
awc->num_samples = read_u32(tag_offset + 0x00,sf);
|
||||
/* 0x04: -1? */
|
||||
awc->sample_rate = read_u16(offset + 0x08,sf);
|
||||
awc->sample_rate = read_u16(tag_offset + 0x08,sf);
|
||||
/* 0x0a: granule start? (negative) */
|
||||
/* 0x0c: granule max? */
|
||||
/* 0x10: unknown */
|
||||
awc->codec = read_u8(offset + 0x1c, sf); /* 16b? */
|
||||
awc->codec = read_u8(tag_offset + 0x1c, sf); /* 16b? */
|
||||
/* 0x1e: vorbis header size */
|
||||
awc->channels = 1;
|
||||
|
||||
awc->vorbis_offset = offset + 0x20;
|
||||
awc->vorbis_offset[0] = tag_offset + 0x20;
|
||||
break;
|
||||
|
||||
case 0xA3: /* block-to-sample table (32b x number of blocks w/ num_samples at the start of each block) */
|
||||
@ -389,6 +393,19 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* vorbis offset table, somehow offsets are unordered and can go before tags */
|
||||
if (awc->is_music && awc->codec == 0x08) {
|
||||
offset += 0x08 * tag_count;
|
||||
|
||||
for (ch = 0; ch < awc->channels; ch++) {
|
||||
awc->vorbis_offset[ch] = read_u16(offset + 0x08*ch + 0x00, sf);
|
||||
/* 0x02: always 0xB000? */
|
||||
/* 0x04: always 0x00CD? */
|
||||
/* 0x06: always 0x7F00? */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* In music mode, data is divided into blocks of block_chunk size with padding.
|
||||
* Each block has a header/seek table and interleaved data for all channels */
|
||||
{
|
||||
@ -404,3 +421,164 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
|
||||
//TODO: this method won't work properly, needs internal handling of blocks.
|
||||
//
|
||||
// This setups a decoder per block, but seems Vorbis' uses first frame as setup so it
|
||||
// returns samples (576 vs 1024), making num_samples count in each block being off + causing
|
||||
// gaps. So they must be using a single encoder + setting decode_to_discard per block
|
||||
// to ge the thing working.
|
||||
//
|
||||
// However since blocks are probably also used for seeking, maybe they aren't resetting
|
||||
// the decoder when seeking? or they force first frame to be 1024?
|
||||
//
|
||||
// In case of Vorvis, when setting skip samples seems repeated data from last block is
|
||||
// exactly last 0x800 bytes of that channel.
|
||||
|
||||
static VGMSTREAM* build_block_vgmstream(STREAMFILE* sf, awc_header* awc, int channel, int32_t num_samples, int32_t skip_samples, off_t block_start, size_t block_size) {
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
int block_channels = 1;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(block_channels, 0);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = awc->sample_rate;
|
||||
vgmstream->num_samples = num_samples - skip_samples;
|
||||
vgmstream->stream_size = block_size;
|
||||
vgmstream->meta_type = meta_AWC;
|
||||
|
||||
switch(awc->codec) {
|
||||
#ifdef VGM_USE_VORBIS
|
||||
case 0x08: { /* Vorbis (PC) [Red Dead Redemption 2 (PC)] */
|
||||
vorbis_custom_config cfg = {0};
|
||||
|
||||
cfg.channels = 1;
|
||||
cfg.sample_rate = awc->sample_rate;
|
||||
cfg.header_offset = awc->vorbis_offset[channel];
|
||||
//cfg.skip_samples = skip_samples; //todo
|
||||
|
||||
vgmstream->codec_data = init_vorbis_custom(sf, block_start, VORBIS_AWC, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->coding_type = coding_VORBIS_custom;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, block_start))
|
||||
goto fail;
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static VGMSTREAM* build_blocks_vgmstream(STREAMFILE* sf, awc_header* awc, int channel) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
segmented_layout_data* data = NULL;
|
||||
int i, ch;
|
||||
int blocks = awc->stream_size / awc->block_chunk + (awc->stream_size % awc->block_chunk ? 1 : 0) ;
|
||||
|
||||
|
||||
/* init layout */
|
||||
data = init_layout_segmented(blocks);
|
||||
if (!data) goto fail;
|
||||
|
||||
/* one segment per block of this channel */
|
||||
for (i = 0; i < blocks; i++) {
|
||||
off_t block_offset = awc->stream_offset + i * awc->block_chunk;
|
||||
int32_t num_samples = 0, skip_samples = 0;
|
||||
uint32_t header_skip = 0, block_skip = 0, block_start = 0, block_data = 0;
|
||||
|
||||
/* read stupid block crap to get proper offsets and whatnot, format:
|
||||
* - per channel: number of channel entries + skip samples + num samples
|
||||
* - per channel: seek table with N entries */
|
||||
for (ch = 0; ch < awc->channels; ch++) {
|
||||
/* 0x00: -1 */
|
||||
int entries = read_u32le(block_offset + 0x18 * ch + 0x04, sf);
|
||||
int32_t entry_skip = read_u32le(block_offset + 0x18 * ch + 0x08, sf);
|
||||
int32_t entry_samples = read_u32le(block_offset + 0x18 * ch + 0x0c, sf);
|
||||
|
||||
if (ch == channel) {
|
||||
num_samples = entry_samples;
|
||||
skip_samples = entry_skip;
|
||||
|
||||
block_start = block_offset + block_skip;
|
||||
block_data = entries * 0x800;
|
||||
}
|
||||
|
||||
header_skip += 0x18 + entries * 0x04;
|
||||
block_skip += entries * 0x800;
|
||||
}
|
||||
|
||||
if (!block_start)
|
||||
goto fail;
|
||||
|
||||
header_skip = align_size_to_block(header_skip, 0x800);
|
||||
block_start += header_skip;
|
||||
//;VGM_LOG("AWC: build ch%i, block=%i at %lx, o=%x, s=%x, ns=%i, ss=%i\n", channel, i, block_offset, block_start, block_data, num_samples, skip_samples);
|
||||
|
||||
data->segments[i] = build_block_vgmstream(sf, awc, channel, num_samples, skip_samples, block_start, block_data);
|
||||
if (!data->segments[i]) goto fail;
|
||||
}
|
||||
|
||||
/* setup VGMSTREAMs */
|
||||
if (!setup_layout_segmented(data))
|
||||
goto fail;
|
||||
|
||||
/* build the layout VGMSTREAM */
|
||||
vgmstream = allocate_segmented_vgmstream(data, 0, 0, 0);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
if (!vgmstream)
|
||||
free_layout_segmented(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
|
||||
/* Make layers per channel for AWC's abhorrent blocks.
|
||||
*
|
||||
* File has N channels = N streams, that use their own mono decoder.
|
||||
* Each block then has header + seek table for all channels. But in each block there is
|
||||
* a "skip samples" value per channel, and blocks repeat some data from last block
|
||||
* for this, so PCM must be discarded. Also, channels in a block don't need to have
|
||||
* the same number of samples.
|
||||
*/
|
||||
static layered_layout_data* build_layered_awc(STREAMFILE* sf, awc_header* awc) {
|
||||
int i;
|
||||
layered_layout_data* data = NULL;
|
||||
|
||||
|
||||
/* init layout */
|
||||
data = init_layout_layered(awc->channels);
|
||||
if (!data) goto fail;
|
||||
|
||||
/* open each layer subfile */
|
||||
for (i = 0; i < awc->channels; i++) {
|
||||
data->layers[i] = build_blocks_vgmstream(sf, awc, i);
|
||||
if (!data->layers[i]) goto fail;
|
||||
}
|
||||
|
||||
/* setup layered VGMSTREAMs */
|
||||
if (!setup_layout_layered(data))
|
||||
goto fail;
|
||||
return data;
|
||||
fail:
|
||||
free_layout_layered(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -1,45 +1,44 @@
|
||||
#include "meta.h"
|
||||
|
||||
|
||||
/* BMP - from Jubeat series (AC) */
|
||||
VGMSTREAM * init_vgmstream_bmp_konami(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .bin: actual extension
|
||||
* .lbin: for plugins */
|
||||
if (!check_extensions(streamFile, "bin,lbin"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) != 0x424D5000) /* "BMP\0" "*/
|
||||
goto fail;
|
||||
|
||||
channel_count = read_8bit(0x10,streamFile); /* assumed */
|
||||
if (channel_count != 2) goto fail;
|
||||
loop_flag = 0;
|
||||
start_offset = 0x20;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_BMP_KONAMI;
|
||||
|
||||
vgmstream->num_samples = read_32bitBE(0x04,streamFile);
|
||||
vgmstream->sample_rate = read_32bitBE(0x14, streamFile);
|
||||
|
||||
vgmstream->coding_type = coding_OKI4S;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
|
||||
|
||||
/* BMP - from Konami arcade games [drummania (AC), GITADORA (AC), Jubeat (AC)] */
|
||||
VGMSTREAM* init_vgmstream_bmp_konami(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channels;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .bin: actual extension
|
||||
* .lbin: for plugins */
|
||||
if (!check_extensions(sf, "bin,lbin"))
|
||||
goto fail;
|
||||
|
||||
if (!is_id32be(0x00,sf, "BMP\0"))
|
||||
goto fail;
|
||||
|
||||
channels = read_u8(0x10,sf); /* assumed */
|
||||
if (channels != 2) goto fail;
|
||||
loop_flag = 0;
|
||||
start_offset = 0x20;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_BMP_KONAMI;
|
||||
|
||||
vgmstream->num_samples = read_u32be(0x04,sf);
|
||||
vgmstream->sample_rate = read_u32be(0x14, sf);
|
||||
|
||||
vgmstream->coding_type = coding_OKI4S;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
67
src/meta/ifs.c
Normal file
67
src/meta/ifs.c
Normal file
@ -0,0 +1,67 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* .ifs - Konami arcade games container [drummania (AC), GITADORA (AC)] */
|
||||
VGMSTREAM* init_vgmstream_ifs(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
off_t subfile_offset, subfile_size;
|
||||
int total_subsongs, target_subsong = sf->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "ifs"))
|
||||
goto fail;
|
||||
|
||||
if (read_u32be(0x00,sf) != 0x6CAD8F89)
|
||||
goto fail;
|
||||
if (read_u16be(0x04,sf) != 0x0003)
|
||||
goto fail;
|
||||
|
||||
/* .ifs format is a binary XML thing with types/fields/nodes/etc, that sometimes
|
||||
* contains Konami's BMP as subsongs (may differ in num_samples). This is an
|
||||
* abridged version of the whole thing, see:
|
||||
* - https://github.com/mon/ifstools
|
||||
* - https://github.com/mon/kbinxml
|
||||
* - https://bitbucket.org/ahigerd/gitadora2wav/
|
||||
*/
|
||||
|
||||
{
|
||||
off_t offset, size, subfile_start;
|
||||
|
||||
subfile_start = read_u32be(0x10,sf);
|
||||
|
||||
/* skip root section and point to childs */
|
||||
offset = 0x28 + 0x04 + read_u32be(0x28,sf);
|
||||
size = read_u32be(offset + 0x00,sf);
|
||||
offset += 0x04;
|
||||
|
||||
/* point to subfile offsets */
|
||||
size = size - 0x04 - read_u32be(offset + 0x00,sf) - 0x04;
|
||||
offset = offset + 0x04 + read_u32be(offset + 0x00,sf) + 0x04;
|
||||
|
||||
total_subsongs = size / 0x0c;
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
|
||||
subfile_offset = read_u32be(offset + (target_subsong-1)*0x0c + 0x00,sf) + subfile_start;
|
||||
subfile_size = read_u32be(offset + (target_subsong-1)*0x0c + 0x04,sf);
|
||||
}
|
||||
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, "bin");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_bmp_konami(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
/* subsongs have names but are packed in six-bit strings */
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
@ -42,7 +42,7 @@ VGMSTREAM* init_vgmstream_wii_was(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_dsp_str_ig(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_dsp_xiii(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_dsp_cabelas(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_wii_ndp(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_dsp_ndp(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_ngc_dsp_aaap(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_dsp_dspw(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_ngc_dsp_iadp(STREAMFILE* sf);
|
||||
@ -56,6 +56,8 @@ VGMSTREAM* init_vgmstream_dsp_ds2(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_dsp_itl(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_dsp_sqex(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_dsp_wiivoice(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_dsp_wiiadpcm(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_dsp_cwac(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_csmp(STREAMFILE *streamFile);
|
||||
|
||||
@ -930,4 +932,6 @@ VGMSTREAM* init_vgmstream_cpk_memory(STREAMFILE* sf, STREAMFILE* sf_acb);
|
||||
|
||||
VGMSTREAM *init_vgmstream_sbk(STREAMFILE *sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_ifs(STREAMFILE* sf);
|
||||
|
||||
#endif /*_META_H*/
|
||||
|
@ -7,19 +7,19 @@
|
||||
* then this is actually how they are laid out in the file, albeit big-endian */
|
||||
struct dsp_header {
|
||||
uint32_t sample_count; /* 0x00 */
|
||||
uint32_t nibble_count; /* 0x04 */
|
||||
uint32_t sample_rate; /* 0x08 */
|
||||
uint32_t nibble_count; /* 0x04 (includes frame headers) */
|
||||
uint32_t sample_rate; /* 0x08 (generally 32/48kz but games like Wario World set 32028hz to adjust for GC's rate) */
|
||||
uint16_t loop_flag; /* 0x0c */
|
||||
uint16_t format; /* 0x0e */
|
||||
uint16_t format; /* 0x0e (always 0 for ADPCM) */
|
||||
uint32_t loop_start_offset; /* 0x10 */
|
||||
uint32_t loop_end_offset; /* 0x14 */
|
||||
uint32_t ca; /* 0x18 */
|
||||
int16_t coef[16]; /* 0x1c (really 8x2) */
|
||||
uint16_t gain; /* 0x3c */
|
||||
uint16_t initial_ps; /* 0x3e */
|
||||
uint32_t ca; /* 0x18 (always 0) */
|
||||
int16_t coef[16]; /* 0x1c (eight pairs) */
|
||||
uint16_t gain; /* 0x3c (always 0 for ADPCM) */
|
||||
uint16_t initial_ps; /* 0x3e (predictor/scale in frame header) */
|
||||
int16_t initial_hist1; /* 0x40 */
|
||||
int16_t initial_hist2; /* 0x42 */
|
||||
uint16_t loop_ps; /* 0x44 */
|
||||
uint16_t loop_ps; /* 0x44 (predictor/scale in loop frame header) */
|
||||
int16_t loop_hist1; /* 0x46 */
|
||||
int16_t loop_hist2; /* 0x48 */
|
||||
int16_t channel_count; /* 0x4a (DSPADPCM.exe ~v2.7 extension) */
|
||||
@ -29,34 +29,35 @@ struct dsp_header {
|
||||
|
||||
/* read the above struct; returns nonzero on failure */
|
||||
static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREAMFILE* sf, int big_endian) {
|
||||
int32_t (*get_32bit)(const uint8_t *) = big_endian ? get_32bitBE : get_32bitLE;
|
||||
int16_t (*get_16bit)(const uint8_t *) = big_endian ? get_16bitBE : get_16bitLE;
|
||||
uint32_t (*get_u32)(const uint8_t*) = big_endian ? get_u32be : get_u32le;
|
||||
uint16_t (*get_u16)(const uint8_t*) = big_endian ? get_u16be : get_u16le;
|
||||
int16_t (*get_s16)(const uint8_t*) = big_endian ? get_s16be : get_s16le;
|
||||
int i;
|
||||
uint8_t buf[0x4e];
|
||||
uint8_t buf[0x60];
|
||||
|
||||
if (offset > get_streamfile_size(sf))
|
||||
return 1;
|
||||
if (read_streamfile(buf, offset, 0x4e, sf) != 0x4e)
|
||||
if (read_streamfile(buf, offset, 0x60, sf) != 0x60)
|
||||
return 1;
|
||||
header->sample_count = get_32bit(buf+0x00);
|
||||
header->nibble_count = get_32bit(buf+0x04);
|
||||
header->sample_rate = get_32bit(buf+0x08);
|
||||
header->loop_flag = get_16bit(buf+0x0c);
|
||||
header->format = get_16bit(buf+0x0e);
|
||||
header->loop_start_offset = get_32bit(buf+0x10);
|
||||
header->loop_end_offset = get_32bit(buf+0x14);
|
||||
header->ca = get_32bit(buf+0x18);
|
||||
header->sample_count = get_u32(buf+0x00);
|
||||
header->nibble_count = get_u32(buf+0x04);
|
||||
header->sample_rate = get_u32(buf+0x08);
|
||||
header->loop_flag = get_u16(buf+0x0c);
|
||||
header->format = get_u16(buf+0x0e);
|
||||
header->loop_start_offset = get_u32(buf+0x10);
|
||||
header->loop_end_offset = get_u32(buf+0x14);
|
||||
header->ca = get_u32(buf+0x18);
|
||||
for (i=0; i < 16; i++)
|
||||
header->coef[i] = get_16bit(buf+0x1c+i*0x02);
|
||||
header->gain = get_16bit(buf+0x3c);
|
||||
header->initial_ps = get_16bit(buf+0x3e);
|
||||
header->initial_hist1 = get_16bit(buf+0x40);
|
||||
header->initial_hist2 = get_16bit(buf+0x42);
|
||||
header->loop_ps = get_16bit(buf+0x44);
|
||||
header->loop_hist1 = get_16bit(buf+0x46);
|
||||
header->loop_hist2 = get_16bit(buf+0x48);
|
||||
header->channel_count = get_16bit(buf+0x4a);
|
||||
header->block_size = get_16bit(buf+0x4c);
|
||||
header->coef[i] = get_s16(buf+0x1c+i*0x02);
|
||||
header->gain = get_u16(buf+0x3c);
|
||||
header->initial_ps = get_u16(buf+0x3e);
|
||||
header->initial_hist1 = get_s16(buf+0x40);
|
||||
header->initial_hist2 = get_s16(buf+0x42);
|
||||
header->loop_ps = get_u16(buf+0x44);
|
||||
header->loop_hist1 = get_s16(buf+0x46);
|
||||
header->loop_hist2 = get_s16(buf+0x48);
|
||||
header->channel_count = get_s16(buf+0x4a);
|
||||
header->block_size = get_s16(buf+0x4c);
|
||||
return 0;
|
||||
}
|
||||
static int read_dsp_header(struct dsp_header *header, off_t offset, STREAMFILE* file) {
|
||||
@ -91,6 +92,7 @@ typedef struct {
|
||||
int fix_loop_start; /* weird files with bad loop start */
|
||||
int single_header; /* all channels share header, thus totals are off */
|
||||
int ignore_header_agreement; /* sometimes there are minor differences between headers */
|
||||
int ignore_loop_ps; /* sometimes has bad loop start ps */
|
||||
} dsp_meta;
|
||||
|
||||
#define COMMON_DSP_MAX_CHANNELS 6
|
||||
@ -113,8 +115,10 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta *dspm) {
|
||||
/* load standard DSP header per channel */
|
||||
{
|
||||
for (i = 0; i < dspm->channel_count; i++) {
|
||||
if (read_dsp_header_endian(&ch_header[i], dspm->header_offset + i*dspm->header_spacing, sf, !dspm->little_endian))
|
||||
if (read_dsp_header_endian(&ch_header[i], dspm->header_offset + i*dspm->header_spacing, sf, !dspm->little_endian)) {
|
||||
//;VGM_LOG("DSP: bad header\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,8 +133,10 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta *dspm) {
|
||||
/* check type==0 and gain==0 */
|
||||
{
|
||||
for (i = 0; i < dspm->channel_count; i++) {
|
||||
if (ch_header[i].format || ch_header[i].gain)
|
||||
if (ch_header[i].format || ch_header[i].gain) {
|
||||
//;VGM_LOG("DSP: bad type/gain\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,26 +162,31 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta *dspm) {
|
||||
|
||||
for (i = 0; i < channels; i++) {
|
||||
off_t channel_offset = dspm->start_offset + i*dspm->interleave;
|
||||
if (ch_header[i].initial_ps != (uint8_t)read_8bit(channel_offset, sf))
|
||||
if (ch_header[i].initial_ps != read_u8(channel_offset, sf)) {
|
||||
//;VGM_LOG("DSP: bad initial ps\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* check expected loop predictor/scale */
|
||||
if (ch_header[0].loop_flag) {
|
||||
if (ch_header[0].loop_flag && !dspm->ignore_loop_ps) {
|
||||
int channels = dspm->channel_count;
|
||||
if (dspm->single_header)
|
||||
channels = 1;
|
||||
|
||||
for (i = 0; i < channels; i++) {
|
||||
off_t loop_offset = ch_header[i].loop_start_offset;
|
||||
|
||||
loop_offset = loop_offset / 0x8 * 0x8; /* loop points to a nibble, but we need closest frame header */
|
||||
if (dspm->interleave) {
|
||||
loop_offset = loop_offset / 16 * 8;
|
||||
loop_offset = (loop_offset / dspm->interleave * dspm->interleave * channels) + (loop_offset % dspm->interleave);
|
||||
}
|
||||
|
||||
if (ch_header[i].loop_ps != (uint8_t)read_8bit(dspm->start_offset + i*dspm->interleave + loop_offset,sf))
|
||||
if (ch_header[i].loop_ps != read_u8(dspm->start_offset + i*dspm->interleave + loop_offset,sf)) {
|
||||
//;VGM_LOG("DSP: bad loop ps: %x vs at %lx\n", ch_header[i].loop_ps, dspm->start_offset + i*dspm->interleave + loop_offset);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,7 +210,7 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta *dspm) {
|
||||
vgmstream->sample_rate = ch_header[0].sample_rate;
|
||||
vgmstream->num_samples = ch_header[0].sample_count;
|
||||
vgmstream->loop_start_sample = dsp_nibbles_to_samples(ch_header[0].loop_start_offset);
|
||||
vgmstream->loop_end_sample = dsp_nibbles_to_samples(ch_header[0].loop_end_offset)+1;
|
||||
vgmstream->loop_end_sample = dsp_nibbles_to_samples(ch_header[0].loop_end_offset) + 1;
|
||||
|
||||
vgmstream->meta_type = dspm->meta_type;
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
@ -224,7 +235,7 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta *dspm) {
|
||||
}
|
||||
}
|
||||
|
||||
/* don't know why, but it does happen*/
|
||||
/* don't know why, but it does happen */
|
||||
if (dspm->fix_looping && vgmstream->loop_end_sample > vgmstream->num_samples)
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
|
||||
@ -235,7 +246,7 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta *dspm) {
|
||||
}
|
||||
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,sf,dspm->start_offset))
|
||||
if (!vgmstream_open_stream(vgmstream, sf, dspm->start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
@ -867,25 +878,28 @@ fail:
|
||||
}
|
||||
|
||||
/* NPD - Icon Games header + subinterleaved DSPs [Vertigo (Wii), Build n' Race (Wii)] */
|
||||
VGMSTREAM* init_vgmstream_wii_ndp(STREAMFILE* sf) {
|
||||
VGMSTREAM* init_vgmstream_dsp_ndp(STREAMFILE* sf) {
|
||||
dsp_meta dspm = {0};
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "ndp"))
|
||||
/* .nds: standard
|
||||
* .ndp: header id */
|
||||
if (!check_extensions(sf, "nds,ndp"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,sf) != 0x4E445000) /* "NDP\0" */
|
||||
if (!is_id32be(0x00,sf, "NDP\0"))
|
||||
goto fail;
|
||||
if (read_32bitLE(0x08,sf) + 0x18 != get_streamfile_size(sf))
|
||||
if (read_u32le(0x08,sf) + 0x18 != get_streamfile_size(sf))
|
||||
goto fail;
|
||||
/* 0x0c: sample rate */
|
||||
|
||||
dspm.channel_count = read_32bitLE(0x10,sf);
|
||||
dspm.channel_count = read_u32le(0x10,sf);
|
||||
dspm.max_channels = 2;
|
||||
|
||||
dspm.header_offset = 0x18;
|
||||
dspm.header_spacing = 0x60;
|
||||
dspm.start_offset = dspm.header_offset + dspm.channel_count*dspm.header_spacing;
|
||||
dspm.interleave = 0x04;
|
||||
dspm.ignore_loop_ps = 1; /* some files loops from 0 but loop ps is null */
|
||||
|
||||
dspm.meta_type = meta_WII_NDP;
|
||||
return init_vgmstream_dsp_common(sf, &dspm);
|
||||
@ -1094,8 +1108,6 @@ VGMSTREAM* init_vgmstream_dsp_sps_n1(STREAMFILE* sf) {
|
||||
dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count;
|
||||
dspm.interleave = 0;
|
||||
|
||||
dspm.fix_loop_start = 1;
|
||||
|
||||
dspm.meta_type = meta_DSP_VAG;
|
||||
return init_vgmstream_dsp_common(sf, &dspm);
|
||||
fail:
|
||||
@ -1306,3 +1318,60 @@ VGMSTREAM* init_vgmstream_dsp_wiivoice(STREAMFILE* sf) {
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* WIIADPCM - Exient wrapper [Need for Speed: Hot Pursuit (Wii)] */
|
||||
VGMSTREAM* init_vgmstream_dsp_wiiadpcm(STREAMFILE* sf) {
|
||||
dsp_meta dspm = {0};
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "adpcm"))
|
||||
goto fail;
|
||||
if (!is_id32be(0x00,sf, "WIIA") && !is_id32be(0x00,sf, "DPCM"))
|
||||
goto fail;
|
||||
|
||||
dspm.interleave = read_u32be(0x08,sf); /* interleave offset */
|
||||
if (dspm.interleave) {
|
||||
dspm.interleave -= 0x10;
|
||||
}
|
||||
/* 0x0c: 0 when RAM (2 DSP headers), interleave size when stream (2 WIIADPCM headers) */
|
||||
|
||||
dspm.channel_count = (dspm.interleave ? 2 : 1);
|
||||
dspm.max_channels = 2;
|
||||
|
||||
dspm.header_offset = 0x10;
|
||||
dspm.header_spacing = dspm.interleave;
|
||||
dspm.start_offset = dspm.header_offset + 0x60;
|
||||
|
||||
dspm.meta_type = meta_DSP_WIIADPCM;
|
||||
return init_vgmstream_dsp_common(sf, &dspm);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* CWAC - CRI wrapper [Mario & Sonic at the Rio 2016 Olympic Games (WiiU)] */
|
||||
VGMSTREAM* init_vgmstream_dsp_cwac(STREAMFILE* sf) {
|
||||
dsp_meta dspm = {0};
|
||||
|
||||
/* checks */
|
||||
/* .dsp: assumed */
|
||||
if (!check_extensions(sf, "dsp"))
|
||||
goto fail;
|
||||
if (!is_id32be(0x00,sf, "CWAC"))
|
||||
goto fail;
|
||||
|
||||
dspm.channel_count = read_u16be(0x04,sf);
|
||||
dspm.header_offset = read_u32be(0x08,sf);
|
||||
dspm.interleave = read_u32be(0x0c,sf) - dspm.header_offset;
|
||||
|
||||
dspm.max_channels = 2;
|
||||
dspm.header_spacing = dspm.interleave;
|
||||
dspm.start_offset = dspm.header_offset + 0x60;
|
||||
dspm.ignore_loop_ps = 1; /* loop offset seems relative to CWAC? also interleave affects it */
|
||||
|
||||
dspm.meta_type = meta_DSP_CWAC;
|
||||
return init_vgmstream_dsp_common(sf, &dspm);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
137
src/meta/nxa.c
137
src/meta/nxa.c
@ -1,58 +1,79 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* Entergram NXA Opus [Higurashi no Naku Koro ni Hou (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_nxa(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag = 0, channel_count;
|
||||
size_t data_size, skip = 0;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "nxa"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, streamFile) != 0x4E584131) /* "NXA1" */
|
||||
goto fail;
|
||||
|
||||
channel_count = read_16bitLE(0x10, streamFile);
|
||||
skip = read_16bitLE(0x16, streamFile);
|
||||
data_size = read_32bitLE(0x08, streamFile)-0x30;
|
||||
start_offset = 0x30;
|
||||
|
||||
/* TODO: Determine if loop points are stored externally. No visible loop points in header */
|
||||
loop_flag = 0;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_NXA;
|
||||
vgmstream->num_samples = read_32bitLE(0x20, streamFile);
|
||||
vgmstream->sample_rate = read_32bitLE(0x0C, streamFile);
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
vgmstream->codec_data = init_ffmpeg_switch_opus(streamFile, start_offset, data_size, vgmstream->channels, skip, vgmstream->sample_rate);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
if (vgmstream->num_samples == 0) {
|
||||
vgmstream->num_samples = switch_opus_get_samples(start_offset, data_size, streamFile) - skip;
|
||||
}
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
/* open the file for reading */
|
||||
if (!vgmstream_open_stream(vgmstream, streamFile, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* Entergram NXA Opus [Higurashi no Naku Koro ni Hou (Switch), Gensou Rougoku no Kaleidoscope (Switch)] */
|
||||
VGMSTREAM* init_vgmstream_opus_nxa(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channels, type, sample_rate;
|
||||
int32_t num_samples, loop_start, loop_end, skip;
|
||||
size_t data_size, frame_size;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "nxa"))
|
||||
goto fail;
|
||||
if (!is_id32be(0x00, sf, "NXA1"))
|
||||
goto fail;
|
||||
|
||||
start_offset = 0x30;
|
||||
type = read_u32le(0x04, sf);
|
||||
data_size = read_u32le(0x08, sf) - start_offset;
|
||||
sample_rate = read_u32le(0x0C, sf);
|
||||
channels = read_s16le(0x10, sf);
|
||||
frame_size = read_u16le(0x12, sf);
|
||||
/* 0x14: frame samples */
|
||||
skip = read_s16le(0x16, sf);
|
||||
num_samples = read_s32le(0x18, sf);
|
||||
loop_start = read_s32le(0x1c, sf);
|
||||
loop_end = read_s32le(0x20, sf);
|
||||
/* rest: null */
|
||||
|
||||
loop_flag = (loop_start > 0);
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_NXA;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
|
||||
switch(type) {
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case 1: { /* Higurashi no Naku Koro ni Hou (Switch) */
|
||||
vgmstream->codec_data = init_ffmpeg_switch_opus(sf, start_offset, data_size, vgmstream->channels, skip, vgmstream->sample_rate);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: { /* Gensou Rougoku no Kaleidoscope (Switch) */
|
||||
opus_config cfg = {0};
|
||||
|
||||
cfg.channels = channels;
|
||||
cfg.skip = skip;
|
||||
cfg.frame_size = frame_size;
|
||||
|
||||
vgmstream->codec_data = init_ffmpeg_fixed_opus(sf, start_offset, data_size, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -610,10 +610,22 @@ static VGMSTREAM* init_subfile(txth_header* txth) {
|
||||
sf_sub = setup_subfile_streamfile(txth->sf_body, txth->subfile_offset, txth->subfile_size, extension);
|
||||
if (!sf_sub) goto fail;
|
||||
|
||||
sf_sub->stream_index = txth->sf->stream_index; /* in case of subfiles with subsongs */
|
||||
sf_sub->stream_index = txth->sf->stream_index;
|
||||
|
||||
vgmstream = init_vgmstream_from_STREAMFILE(sf_sub);
|
||||
if (!vgmstream) goto fail;
|
||||
if (!vgmstream) {
|
||||
/* In case of subfiles with subsongs pass subsong N by default (ex. subfile is a .fsb with N subsongs).
|
||||
* But if the subfile is a single-subsong subfile (ex. subfile is a .fsb with 1 subsong) try again
|
||||
* without passing index (as it would fail first trying to open subsong N). */
|
||||
if (sf_sub->stream_index > 1) {
|
||||
sf_sub->stream_index = 0;
|
||||
vgmstream = init_vgmstream_from_STREAMFILE(sf_sub);
|
||||
if (!vgmstream) goto fail;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* apply some fields */
|
||||
if (txth->sample_rate)
|
||||
|
@ -3,72 +3,76 @@
|
||||
#include "../util.h"
|
||||
|
||||
/* BNS - Wii "Banner Sound" disc jingle */
|
||||
VGMSTREAM * init_vgmstream_wii_bns(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
char filename[PATH_LIMIT];
|
||||
off_t BNS_offset;
|
||||
uint32_t info_offset=0,data_offset=0;
|
||||
VGMSTREAM* init_vgmstream_wii_bns(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t bns_offset;
|
||||
uint32_t info_offset = 0, data_offset = 0;
|
||||
uint32_t channel_info_offset_list_offset;
|
||||
int channel_count;
|
||||
int loop_flag;
|
||||
uint16_t sample_rate;
|
||||
int channels, loop_flag, sample_rate;
|
||||
uint32_t sample_count, loop_start;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
if (strcasecmp("bns",filename_extension(filename))) goto fail;
|
||||
/* checks */
|
||||
/* .bin: actual extension
|
||||
* .bns: header id */
|
||||
if (!check_extensions(sf, "bin,lbin,bns"))
|
||||
goto fail;
|
||||
|
||||
// check header
|
||||
BNS_offset = 0;
|
||||
if (read_32bitBE(BNS_offset,streamFile) == 0x494D4435) // IMD5
|
||||
{
|
||||
// Skip IMD5 header if present
|
||||
BNS_offset = 0x20;
|
||||
bns_offset = 0;
|
||||
if (is_id32be(bns_offset + 0x40, sf, "IMET")) {
|
||||
/* regular .bnr, find sound.bin offset */
|
||||
bns_offset = read_u32be(bns_offset + 0x44,sf);
|
||||
|
||||
/* tables, probably not ok for all cases */
|
||||
bns_offset += read_u32be(bns_offset + 0x54,sf);
|
||||
}
|
||||
|
||||
if (read_32bitBE(BNS_offset+0x00,streamFile) != 0x424E5320) goto fail; // "BNS "
|
||||
if ((uint32_t)read_32bitBE(BNS_offset+0x04,streamFile) != 0xFEFF0100u) goto fail;
|
||||
if (is_id32be(bns_offset + 0x00, sf, "IMD5")) {
|
||||
/* skip IMD5 header if present */
|
||||
bns_offset += 0x20;
|
||||
}
|
||||
|
||||
// find chunks, verify
|
||||
if (!is_id32be(bns_offset + 0x00,sf, "BNS "))
|
||||
goto fail;
|
||||
if (read_u32be(bns_offset + 0x04,sf) != 0xFEFF0100u)
|
||||
goto fail;
|
||||
|
||||
/* find chunks */
|
||||
{
|
||||
// file size as claimed by header
|
||||
uint32_t header_file_size = read_32bitBE(BNS_offset+0x08,streamFile);
|
||||
|
||||
uint32_t header_size = read_16bitBE(BNS_offset+0xc,streamFile);
|
||||
uint16_t chunk_count = read_16bitBE(BNS_offset+0xe,streamFile);
|
||||
|
||||
uint32_t file_size = read_u32be(bns_offset+0x08,sf);
|
||||
uint32_t header_size = read_u16be(bns_offset+0x0c,sf);
|
||||
uint16_t chunk_count = read_u16be(bns_offset+0x0e,sf);
|
||||
int i;
|
||||
|
||||
// assume BNS is the last thing in the file
|
||||
if (header_file_size + BNS_offset != get_streamfile_size(streamFile))
|
||||
/* assume BNS is the last thing in the file */
|
||||
if (file_size + bns_offset != get_streamfile_size(sf))
|
||||
goto fail;
|
||||
|
||||
for (i = 0; i < chunk_count; i++) {
|
||||
uint32_t chunk_info_offset = BNS_offset+0x10+i*8;
|
||||
uint32_t chunk_info_offset = bns_offset+0x10+i*0x08;
|
||||
uint32_t chunk_offset, chunk_size;
|
||||
|
||||
// ensure chunk info is within header
|
||||
if (chunk_info_offset+8 > BNS_offset+header_size) goto fail;
|
||||
if (chunk_info_offset+8 > bns_offset+header_size) goto fail;
|
||||
|
||||
chunk_offset = BNS_offset + (uint32_t)read_32bitBE(chunk_info_offset,streamFile);
|
||||
chunk_size = (uint32_t)read_32bitBE(chunk_info_offset+4,streamFile);
|
||||
chunk_offset = bns_offset + (uint32_t)read_32bitBE(chunk_info_offset,sf);
|
||||
chunk_size = (uint32_t)read_32bitBE(chunk_info_offset+4,sf);
|
||||
|
||||
// ensure chunk is within file
|
||||
if (chunk_offset < BNS_offset+header_size ||
|
||||
chunk_offset+chunk_size > BNS_offset+header_file_size) goto fail;
|
||||
if (chunk_offset < bns_offset+header_size ||
|
||||
chunk_offset+chunk_size > bns_offset+file_size) goto fail;
|
||||
|
||||
// ensure chunk size in header matches that listed in chunk
|
||||
// Note: disabled for now, as the Homebrew Channel BNS has a DATA
|
||||
// chunk that doesn't include the header size
|
||||
//if ((uint32_t)read_32bitBE(chunk_offset+4,streamFile) != chunk_size) goto fail;
|
||||
//if ((uint32_t)read_32bitBE(chunk_offset+4,sf) != chunk_size) goto fail;
|
||||
|
||||
// handle each chunk type
|
||||
switch (read_32bitBE(chunk_offset,streamFile)) {
|
||||
switch (read_32bitBE(chunk_offset,sf)) {
|
||||
case 0x494E464F: // INFO
|
||||
info_offset = chunk_offset+8;
|
||||
info_offset = chunk_offset+0x08;
|
||||
break;
|
||||
case 0x44415441: // DATA
|
||||
data_offset = chunk_offset+8;
|
||||
data_offset = chunk_offset+0x08;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
@ -82,72 +86,65 @@ VGMSTREAM * init_vgmstream_wii_bns(STREAMFILE *streamFile) {
|
||||
/* parse out basic stuff in INFO */
|
||||
{
|
||||
/* only seen this zero, specifies DSP format? */
|
||||
if (read_8bit(info_offset+0x00,streamFile) != 0) goto fail;
|
||||
if (read_8bit(info_offset+0x00,sf) != 0) goto fail;
|
||||
|
||||
loop_flag = read_8bit(info_offset+0x01,streamFile);
|
||||
channel_count = read_8bit(info_offset+0x02,streamFile);
|
||||
loop_flag = read_8bit(info_offset+0x01,sf);
|
||||
channels = read_8bit(info_offset+0x02,sf);
|
||||
|
||||
/* only seen zero, padding? */
|
||||
if (read_8bit(info_offset+0x03,streamFile) != 0) goto fail;
|
||||
if (read_8bit(info_offset+0x03,sf) != 0) goto fail;
|
||||
|
||||
sample_rate = (uint16_t)read_16bitBE(info_offset+0x04,streamFile);
|
||||
sample_rate = read_u16be(info_offset+0x04,sf);
|
||||
|
||||
/* only seen this zero, padding? */
|
||||
if (read_16bitBE(info_offset+0x06,streamFile) != 0) goto fail;
|
||||
if (read_u16be(info_offset+0x06,sf) != 0) goto fail;
|
||||
|
||||
loop_start = read_32bitBE(info_offset+0x08,streamFile);
|
||||
loop_start = read_s32be(info_offset+0x08,sf);
|
||||
|
||||
sample_count = read_32bitBE(info_offset+0x0c,streamFile);
|
||||
sample_count = read_s32be(info_offset+0x0c,sf);
|
||||
|
||||
channel_info_offset_list_offset = info_offset + (uint32_t)read_32bitBE(info_offset+0x10,streamFile);
|
||||
channel_info_offset_list_offset = info_offset + read_u32be(info_offset+0x10,sf);
|
||||
}
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* fill in the vital statistics */
|
||||
vgmstream->channels = channel_count;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->num_samples = sample_count;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->meta_type = meta_WII_BNS;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = sample_count;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = sample_count;
|
||||
|
||||
if (loop_flag)
|
||||
{
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = sample_count;
|
||||
}
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
//todo cleanup
|
||||
/* open the file for reading */
|
||||
{
|
||||
int i;
|
||||
for (i=0;i<channel_count;i++) {
|
||||
uint32_t channel_info_offset = info_offset + read_32bitBE(channel_info_offset_list_offset+4*i,streamFile);
|
||||
uint32_t channel_data_offset = data_offset + (uint32_t)read_32bitBE(channel_info_offset+0,streamFile);
|
||||
uint32_t channel_dsp_offset = info_offset + (uint32_t)read_32bitBE(channel_info_offset+4,streamFile);
|
||||
int j;
|
||||
int i, j;
|
||||
for (i = 0; i < channels; i++) {
|
||||
uint32_t channel_info_offset = info_offset + read_u32be(channel_info_offset_list_offset + 0x04*i,sf);
|
||||
uint32_t channel_data_offset = data_offset + read_u32be(channel_info_offset + 0x00,sf);
|
||||
uint32_t channel_dsp_offset = info_offset + read_u32be(channel_info_offset + 0x04,sf);
|
||||
|
||||
/* always been 0... */
|
||||
if (read_32bitBE(channel_info_offset+8,streamFile) != 0) goto fail;
|
||||
if (read_u32be(channel_info_offset + 0x8,sf) != 0) goto fail;
|
||||
|
||||
vgmstream->ch[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
vgmstream->ch[i].streamfile = reopen_streamfile(sf, 0);
|
||||
if (!vgmstream->ch[i].streamfile) goto fail;
|
||||
vgmstream->ch[i].channel_start_offset = vgmstream->ch[i].offset = channel_data_offset;
|
||||
|
||||
vgmstream->ch[i].channel_start_offset=
|
||||
vgmstream->ch[i].offset=channel_data_offset;
|
||||
|
||||
for (j=0;j<16;j++)
|
||||
vgmstream->ch[i].adpcm_coef[j] =
|
||||
read_16bitBE(channel_dsp_offset+j*2,streamFile);
|
||||
for (j = 0; j < 16; j++) {
|
||||
vgmstream->ch[i].adpcm_coef[j] = read_s16be(channel_dsp_offset + j*0x02,sf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
// goto fail;
|
||||
return vgmstream;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -664,7 +664,7 @@ void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double
|
||||
* pre1 start1 end1 post1
|
||||
* - when pre and post are set nothing is done (fade is exact and multiple fades may overlap)
|
||||
* - when previous fade's post or current fade's pre are negative (meaning file end/start)
|
||||
* they should cancel each other (to allow changing fade-in + fade-out + fade-in + etc):
|
||||
* they should cancel each other (to allow chaining fade-in + fade-out + fade-in + etc):
|
||||
* <----------|----------|----------| |----------|----------|---------->
|
||||
* pre1 start1 end1 post1 pre2 start2 end2 post2
|
||||
* - other cases (previous fade is actually after/in-between current fade) are ignored
|
||||
|
@ -81,7 +81,7 @@ void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM
|
||||
strncpy(buf, pos, buf_len);
|
||||
|
||||
/* name without extension */
|
||||
if (cfg->remove_extension) {
|
||||
if (cfg && cfg->remove_extension) {
|
||||
pos2 = strrchr(buf, '.');
|
||||
if (pos2 && strlen(pos2) < 15) /* too big extension = file name probably has a dot in the middle */
|
||||
pos2[0] = '\0';
|
||||
|
@ -307,6 +307,10 @@ static inline int min_s32(int32_t a, int32_t b) { return a < b ? a : b; }
|
||||
//align32, align16, clamp16, etc
|
||||
#endif
|
||||
|
||||
static inline const int is_id32be(off_t offset, STREAMFILE* sf, const char* s) {
|
||||
return read_u32be(offset, sf) == get_id32be(s);
|
||||
}
|
||||
|
||||
//TODO: maybe move to streamfile.c
|
||||
/* guess byte endianness from a given value, return true if big endian and false if little endian */
|
||||
static inline int guess_endianness16bit(off_t offset, STREAMFILE* sf) {
|
||||
|
@ -98,6 +98,10 @@ static inline int clamp16(int32_t val) {
|
||||
else return val;
|
||||
}
|
||||
|
||||
static inline const uint32_t get_id32be(const char* s) {
|
||||
return (uint32_t)(s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0);
|
||||
}
|
||||
|
||||
/* less common functions, no need to inline */
|
||||
|
||||
int round10(int val);
|
||||
|
@ -200,7 +200,7 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
|
||||
init_vgmstream_redspark,
|
||||
init_vgmstream_ivaud,
|
||||
init_vgmstream_wii_wsd,
|
||||
init_vgmstream_wii_ndp,
|
||||
init_vgmstream_dsp_ndp,
|
||||
init_vgmstream_ps2_sps,
|
||||
init_vgmstream_ps2_xa2_rrp,
|
||||
init_vgmstream_nds_hwas,
|
||||
@ -513,6 +513,9 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
|
||||
init_vgmstream_cpk,
|
||||
init_vgmstream_opus_nsopus,
|
||||
init_vgmstream_sbk,
|
||||
init_vgmstream_dsp_wiiadpcm,
|
||||
init_vgmstream_dsp_cwac,
|
||||
init_vgmstream_ifs,
|
||||
|
||||
/* 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 */
|
||||
|
@ -742,6 +742,8 @@ typedef enum {
|
||||
meta_DSP_SQEX,
|
||||
meta_DSP_WIIVOICE,
|
||||
meta_SBK,
|
||||
meta_DSP_WIIADPCM,
|
||||
meta_DSP_CWAC,
|
||||
} meta_t;
|
||||
|
||||
/* standard WAVEFORMATEXTENSIBLE speaker positions */
|
||||
|
Loading…
x
Reference in New Issue
Block a user