mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-02-17 19:19:16 +01:00
commit
f920e65cfc
68
doc/TXTH.md
68
doc/TXTH.md
@ -49,7 +49,7 @@ The following can be used in place of `(value)` for `(key) = (value)` commands.
|
||||
* `$1|2|3|4`: value has size of 8/16/24/32 bit (optional, defaults to 4)
|
||||
* Example: `@0x10:BE$2` means `get big endian 16b value at 0x10`
|
||||
- `(field)`: uses current value of some fields. Accepted strings:
|
||||
- `interleave, interleave_last, channels, sample_rate, start_offset, data_size, num_samples, loop_start_sample, loop_end_sample, subsong_count, subsong_offset, subfile_offset, subfile_size, base_offset, name_valueX`
|
||||
- `interleave, interleave_last, channels, sample_rate, start_offset, data_size, num_samples, loop_start_sample, loop_end_sample, subsong_count, subsong_spacing, subfile_offset, subfile_size, base_offset, name_valueX`
|
||||
- `(other)`: other special values for certain keys, described per key
|
||||
|
||||
|
||||
@ -256,8 +256,12 @@ sample_type = samples|bytes|blocks
|
||||
```
|
||||
|
||||
#### SAMPLE VALUES [REQUIRED (num_samples)]
|
||||
Those tell vgmstream how long the song is. Define loop points for the track to repeat at those points (if plugin is configured to loop).
|
||||
|
||||
You can use `loop_start` and `loop_end` instead as aliases of `loop_start_sample` and `loop_end_sample` (no difference).
|
||||
|
||||
Special values:
|
||||
- `data_size`: automatically converts bytes-to-samples
|
||||
- `data_size`: automatically converts bytes-to-samples (a few codecs don't allow this)
|
||||
```
|
||||
num_samples = (value)|data_size
|
||||
loop_start_sample = (value)
|
||||
@ -347,12 +351,14 @@ body_file = (filename)|*.(extension)|null
|
||||
```
|
||||
|
||||
#### SUBSONGS
|
||||
Sets the number of subsongs in the file, adjusting reads per subsong N: `value = @(offset) + subsong_offset*N`. number/constants values aren't adjusted though.
|
||||
Sets the number of subsongs in the file, adjusting reads per subsong N: `value = @(offset) + subsong_spacing*N`. Number/constants values aren't adjusted though.
|
||||
|
||||
Instead of `subsong_spacing` you can use `subsong_offset` (older alias).
|
||||
|
||||
Mainly for bigfiles with consecutive headers per subsong, set subsong_offset to 0 when done as it affects any reads. The current subsong number is handled externally by plugins or TXTP.
|
||||
```
|
||||
subsong_count = (value)
|
||||
subsong_offset = (value)
|
||||
subsong_spacing = (value)
|
||||
```
|
||||
|
||||
#### NAMES
|
||||
@ -360,10 +366,11 @@ Sets the name of the stream, most useful when used with subsongs. TXTH will read
|
||||
|
||||
`name_size` defaults to 0, which reads until null-terminator or a non-ascii character is found.
|
||||
|
||||
`name_offset` can be a (number) value, but being an offset it's also adjusted by subsong_offset.
|
||||
`name_offset` can be a (number) value, but being an offset it's also adjusted by `subsong_spacing`. If you need to point to some absolute offset (for example a subsong pointings to name in another table) that doesn't depend on subsong (must not be changed by `subsong_spacing`), use `name_offset_absolute`.
|
||||
```
|
||||
name_offset = (value)
|
||||
name_size = (value)
|
||||
name_offset_absolute = (value)
|
||||
```
|
||||
|
||||
#### SUBFILES
|
||||
@ -513,7 +520,7 @@ Note that DSP coefs are special in that aren't read immediately, and will use *l
|
||||
Values may need to be reset (to 0 or other sensible value) when done. Subsong example:
|
||||
```
|
||||
subsong_count = 5
|
||||
subsong_offset = 0x20 # there are 5 subsong headers, 0x20 each
|
||||
subsong_spacing = 0x20 # there are 5 subsong headers, 0x20 each
|
||||
channel_count = @0x10 # reads channels at 0x10+0x20*subsong
|
||||
# 1st subsong: 0x10+0x20*0: 0x10
|
||||
# 2nd subsong: 0x10+0x20*1: 0x30
|
||||
@ -521,7 +528,7 @@ channel_count = @0x10 # reads channels at 0x10+0x20*subsong
|
||||
# ...
|
||||
start_offset = @0x14 # reads offset within data at 0x14+0x20*subsong
|
||||
|
||||
subsong_offset = 0 # reset value
|
||||
subsong_spacing = 0 # reset value
|
||||
sample_rate = 0x04 # sample rate is the same for all subsongs
|
||||
# Nth subsong ch: 0x04+0x00*N: 0x08
|
||||
```
|
||||
@ -784,7 +791,7 @@ channels = 2
|
||||
|
||||
# subsong headers at 0x1A5A40, entry size 0x14, total 58 * 0x14 = 0x488
|
||||
subsong_count = 58
|
||||
subsong_offset = 0x14
|
||||
subsong_spacing = 0x14
|
||||
base_offset = 0x1A5A40
|
||||
|
||||
sample_rate = @0x00
|
||||
@ -847,14 +854,13 @@ JIN002.XAG: 0x168
|
||||
JIN003.XAG: 0x180
|
||||
```
|
||||
|
||||
|
||||
#### Grandia (PS1) bgm.txth
|
||||
```
|
||||
header_file = GM1.IDX
|
||||
body_file = GM1.STZ
|
||||
|
||||
subsong_count = 394 #last doesn't have size though
|
||||
subsong_offset = 0x04
|
||||
subsong_spacing = 0x04
|
||||
|
||||
subfile_offset = (@0x00 & 0xFFFFF) * 0x800
|
||||
subfile_extension = seb
|
||||
@ -897,7 +903,6 @@ st_s01_02b.ssd: 6*0x04
|
||||
st_s01_02c.ssd: 7*0x04
|
||||
```
|
||||
|
||||
|
||||
#### Zack & Wiki (Wii) st_s01_00a.txth
|
||||
```
|
||||
#alt from above with untouched folders
|
||||
@ -937,7 +942,7 @@ coef_endianness = BE
|
||||
# uses wildcards for full paths from plugins
|
||||
```
|
||||
|
||||
#### Croc (SAT) .asf.txth
|
||||
#### Croc (SAT) .asf.txth
|
||||
```
|
||||
codec = ASF
|
||||
sample_rate = 22050
|
||||
@ -945,7 +950,7 @@ channels = 2
|
||||
num_samples = data_size
|
||||
```
|
||||
|
||||
#### Sega Rally 3 (SAT) ALL_SOUND.txth
|
||||
#### Sega Rally 3 (SAT) ALL_SOUND.txth
|
||||
```
|
||||
codec = PCM16LE
|
||||
|
||||
@ -968,12 +973,12 @@ body_file = ALL_AUDIO.sfx
|
||||
# read stream header using table3
|
||||
subsong_count = @0x114
|
||||
base_offset = @0x110
|
||||
subsong_offset = 0xc8
|
||||
subsong_spacing = 0xc8
|
||||
|
||||
name_offset = 0x00
|
||||
#0xc0: file number
|
||||
base_offset = @0xc4 #absolute jump
|
||||
subsong_offset = 0 #stop offsetting for next vals
|
||||
subsong_spacing = 0 #stop offsetting for next vals
|
||||
|
||||
channels = @0xC0
|
||||
sample_rate = @0xC4
|
||||
@ -983,11 +988,12 @@ num_samples = data_size
|
||||
# read stream offset using table4
|
||||
base_offset = 0 #reset current jump
|
||||
base_offset = @0x118
|
||||
subsong_offset = 0xc8
|
||||
subsong_spacing = 0xc8
|
||||
|
||||
start_offset = @0xc4 + 0xc0
|
||||
```
|
||||
#### Sega Rally 3 (PC) EnglishStream.txth
|
||||
|
||||
#### Sega Rally 3 (PC) EnglishStream.txth
|
||||
```
|
||||
codec = PCM16LE
|
||||
|
||||
@ -1005,12 +1011,12 @@ body_file = EnglishStreamData.stm
|
||||
# read stream header using table1
|
||||
subsong_count = @0x104
|
||||
base_offset = @0x100
|
||||
subsong_offset = 0xc8
|
||||
subsong_spacing = 0xc8
|
||||
|
||||
name_offset = 0x00
|
||||
#0xc0: file number
|
||||
base_offset = @0xc4 #absolute jump
|
||||
subsong_offset = 0 #stop offsetting for next vals
|
||||
subsong_spacing = 0 #stop offsetting for next vals
|
||||
|
||||
channels = @0xC0
|
||||
sample_rate = @0xC4
|
||||
@ -1020,7 +1026,29 @@ num_samples = data_size
|
||||
# read stream offset using table1
|
||||
base_offset = 0 #reset current jump
|
||||
base_offset = @0x108
|
||||
subsong_offset = 0xc8
|
||||
subsong_spacing = 0xc8
|
||||
|
||||
start_offset = @0xc4 + 0xc0
|
||||
```
|
||||
|
||||
#### Starsky & Hutch (PS2) MUSICPS2.WAD.txth
|
||||
```
|
||||
codec = PSX
|
||||
channels = 1
|
||||
sample_type = bytes
|
||||
|
||||
header_file = MUSICPS2.WAD
|
||||
body_file = MUSICPS2.WAD
|
||||
|
||||
subsong_count = 0xC
|
||||
subsong_spacing = 0x30
|
||||
sample_rate = 32000
|
||||
base_offset = 0x70
|
||||
start_offset = @0x14 + 0x380
|
||||
num_samples = @0x18
|
||||
data_size = num_samples
|
||||
loop_flag = auto
|
||||
|
||||
#@0x10 is an absolute offset to another table, that shouldn't be affected by subsong_spacing
|
||||
name_offset_absolute = @0x10 + 0x270
|
||||
```
|
||||
|
@ -540,14 +540,18 @@ typedef struct {
|
||||
int coupled_count;
|
||||
int stream_count;
|
||||
int channel_mapping[8];
|
||||
/* frame table */
|
||||
off_t table_offset;
|
||||
int table_count;
|
||||
} opus_config;
|
||||
|
||||
ffmpeg_codec_data* init_ffmpeg_switch_opus_config(STREAMFILE* sf, off_t start_offset, size_t data_size, opus_config* cfg);
|
||||
ffmpeg_codec_data* init_ffmpeg_switch_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
|
||||
ffmpeg_codec_data* init_ffmpeg_ue4_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
|
||||
ffmpeg_codec_data* init_ffmpeg_ea_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
|
||||
ffmpeg_codec_data* init_ffmpeg_x_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate);
|
||||
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 table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip);
|
||||
|
||||
size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE* sf);
|
||||
|
||||
|
@ -17,13 +17,12 @@
|
||||
* https://github.com/hcs64/ww2ogg
|
||||
*/
|
||||
|
||||
typedef enum { OPUS_SWITCH, OPUS_UE4_v1, OPUS_UE4_v2, OPUS_EA, OPUS_X, OPUS_FSB } opus_type_t;
|
||||
typedef enum { OPUS_SWITCH, OPUS_UE4_v1, OPUS_UE4_v2, OPUS_EA, OPUS_X, OPUS_FSB, OPUS_WWISE } 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);
|
||||
static size_t opus_get_packet_samples(const uint8_t *buf, int len);
|
||||
static size_t opus_get_packet_samples_sf(STREAMFILE* sf, off_t offset);
|
||||
static size_t get_xopus_packet_size(int packet, STREAMFILE* sf);
|
||||
static opus_type_t get_ue4opus_version(STREAMFILE* sf, off_t offset);
|
||||
|
||||
typedef struct {
|
||||
@ -32,6 +31,11 @@ typedef struct {
|
||||
off_t stream_offset;
|
||||
size_t stream_size;
|
||||
|
||||
/* list of OPUS frame sizes, for variations that preload this (must alloc/dealloc on init/close) */
|
||||
off_t table_offset;
|
||||
int table_count;
|
||||
uint16_t* frame_table;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* offset that corresponds to physical_offset */
|
||||
off_t physical_offset; /* actual file offset */
|
||||
@ -46,8 +50,11 @@ typedef struct {
|
||||
size_t head_size; /* OggS head page size */
|
||||
|
||||
size_t logical_size;
|
||||
|
||||
} opus_io_data;
|
||||
|
||||
static size_t get_table_frame_size(opus_io_data* data, int packet);
|
||||
|
||||
|
||||
/* Convers custom Opus packets to Ogg Opus, so the resulting data is larger than physical data. */
|
||||
static size_t opus_io_read(STREAMFILE* sf, uint8_t *dest, off_t offset, size_t length, opus_io_data* data) {
|
||||
@ -59,12 +66,12 @@ static size_t opus_io_read(STREAMFILE* sf, uint8_t *dest, off_t offset, size_t l
|
||||
}
|
||||
|
||||
/* previous offset: re-start as we can't map logical<>physical offsets */
|
||||
if (offset < data->logical_offset) {
|
||||
if (offset < data->logical_offset || data->logical_offset < 0) {
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->page_size = 0;
|
||||
data->samples_done = 0;
|
||||
data->sequence = 2; /* appended header is 0/1 */
|
||||
data->sequence = 2; /* appended header+comment is 0/1 */
|
||||
|
||||
if (offset >= data->head_size)
|
||||
data->logical_offset = data->head_size;
|
||||
@ -119,7 +126,8 @@ static size_t opus_io_read(STREAMFILE* sf, uint8_t *dest, off_t offset, size_t l
|
||||
skip_size = 0x02;
|
||||
break;
|
||||
case OPUS_X:
|
||||
data_size = get_xopus_packet_size(data->sequence - 2, sf);
|
||||
case OPUS_WWISE:
|
||||
data_size = get_table_frame_size(data, data->sequence - 2);
|
||||
skip_size = 0;
|
||||
break;
|
||||
default:
|
||||
@ -132,7 +140,7 @@ static size_t opus_io_read(STREAMFILE* sf, uint8_t *dest, off_t offset, size_t l
|
||||
data->page_size = oggs_size + data_size;
|
||||
|
||||
if (data->page_size > sizeof(data->page_buffer)) { /* happens on bad reads/EOF too */
|
||||
VGM_LOG("OPUS: buffer can't hold OggS at %x\n", (uint32_t)data->physical_offset);
|
||||
VGM_LOG("OPUS: buffer can't hold OggS at %x, size=%x\n", (uint32_t)data->physical_offset, data->page_size);
|
||||
data->page_size = 0;
|
||||
break;
|
||||
}
|
||||
@ -219,7 +227,8 @@ static size_t opus_io_size(STREAMFILE* sf, opus_io_data* data) {
|
||||
skip_size = 0x02;
|
||||
break;
|
||||
case OPUS_X:
|
||||
data_size = get_xopus_packet_size(packet, sf);
|
||||
case OPUS_WWISE:
|
||||
data_size = get_table_frame_size(data, packet);
|
||||
skip_size = 0x00;
|
||||
break;
|
||||
default:
|
||||
@ -253,39 +262,62 @@ static size_t opus_io_size(STREAMFILE* sf, opus_io_data* data) {
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
static int opus_io_init(STREAMFILE* sf, opus_io_data* data) {
|
||||
//;VGM_LOG("OPUS: init\n");
|
||||
|
||||
/* read table containing frame sizes */
|
||||
if (data->table_count) {
|
||||
int i;
|
||||
//;VGM_LOG("OPUS: reading table, offset=%lx, entries=%i\n", data->table_offset, data->table_count);
|
||||
|
||||
data->frame_table = malloc(data->table_count * sizeof(uint16_t));
|
||||
if (!data->frame_table) goto fail;
|
||||
|
||||
for (i = 0; i < data->table_count; i++) {
|
||||
data->frame_table[i] = read_u16le(data->table_offset + i * 0x02, sf);
|
||||
}
|
||||
}
|
||||
|
||||
data->logical_offset = -1; /* force reset in case old data was cloned when re-opening SFs */
|
||||
data->logical_size = opus_io_size(sf, data); /* force size */
|
||||
return 1;
|
||||
fail:
|
||||
free(data->frame_table);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void opus_io_close(STREAMFILE* sf, opus_io_data* data) {
|
||||
//;VGM_LOG("OPUS: closing\n");
|
||||
|
||||
free(data->frame_table);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 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) {
|
||||
STREAMFILE* temp_sf = NULL, *new_sf = NULL;
|
||||
STREAMFILE* new_sf = NULL;
|
||||
opus_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(opus_io_data);
|
||||
|
||||
if (!cfg->sample_rate)
|
||||
cfg->sample_rate = 48000; /* default / only value for opus */
|
||||
|
||||
io_data.type = type;
|
||||
io_data.stream_offset = stream_offset;
|
||||
io_data.stream_size = stream_size;
|
||||
io_data.physical_offset = stream_offset;
|
||||
io_data.table_offset = cfg->table_offset;
|
||||
io_data.table_count = cfg->table_count;
|
||||
|
||||
io_data.head_size = make_oggs_first(io_data.head_buffer, sizeof(io_data.head_buffer), cfg);
|
||||
if (!io_data.head_size) goto fail;
|
||||
io_data.sequence = 2;
|
||||
io_data.logical_size = opus_io_size(sf, &io_data); /* force init */
|
||||
|
||||
/* setup subfile */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
if (!new_sf) goto fail;
|
||||
temp_sf = new_sf;
|
||||
|
||||
new_sf = open_io_streamfile(temp_sf, &io_data,io_data_size, opus_io_read,opus_io_size);
|
||||
if (!new_sf) goto fail;
|
||||
temp_sf = new_sf;
|
||||
|
||||
new_sf = open_buffer_streamfile(new_sf,0);
|
||||
if (!new_sf) goto fail;
|
||||
temp_sf = new_sf;
|
||||
|
||||
return temp_sf;
|
||||
|
||||
new_sf = open_io_streamfile_ex_f(new_sf, &io_data, sizeof(opus_io_data), opus_io_read, opus_io_size, opus_io_init, opus_io_close);
|
||||
new_sf = open_buffer_streamfile_f(new_sf, 0);
|
||||
return new_sf;
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -328,7 +360,7 @@ static uint32_t crc_lookup[256]={
|
||||
};
|
||||
|
||||
/* from ww2ogg */
|
||||
static uint32_t get_oggs_checksum(uint8_t * data, int bytes) {
|
||||
static uint32_t get_oggs_checksum(uint8_t* data, int bytes) {
|
||||
uint32_t crc_reg=0;
|
||||
int i;
|
||||
|
||||
@ -339,7 +371,7 @@ static uint32_t get_oggs_checksum(uint8_t * data, int bytes) {
|
||||
}
|
||||
|
||||
/* from opus_decoder.c's opus_packet_get_samples_per_frame */
|
||||
static uint32_t opus_packet_get_samples_per_frame(const uint8_t * data, int Fs) {
|
||||
static uint32_t opus_packet_get_samples_per_frame(const uint8_t* data, int Fs) {
|
||||
int audiosize;
|
||||
if (data[0]&0x80)
|
||||
{
|
||||
@ -359,7 +391,7 @@ static uint32_t opus_packet_get_samples_per_frame(const uint8_t * data, int Fs)
|
||||
}
|
||||
|
||||
/* from opus_decoder.c's opus_packet_get_nb_frames */
|
||||
static int opus_packet_get_nb_frames(const uint8_t * packet, int len) {
|
||||
static int opus_packet_get_nb_frames(const uint8_t* packet, int len) {
|
||||
int count;
|
||||
if (len<1)
|
||||
return 0;
|
||||
@ -376,7 +408,7 @@ static int opus_packet_get_nb_frames(const uint8_t * packet, int len) {
|
||||
|
||||
/* ******************************** */
|
||||
|
||||
static size_t make_oggs_page(uint8_t * buf, int buf_size, size_t data_size, int page_sequence, int granule) {
|
||||
static size_t make_oggs_page(uint8_t* buf, int buf_size, size_t data_size, int page_sequence, int granule) {
|
||||
size_t page_done, lacing_done = 0;
|
||||
uint64_t absolute_granule = granule; /* wrong values seem validated (0, less than real samples, etc) */
|
||||
int header_type_flag = (page_sequence==0 ? 2 : 0);
|
||||
@ -390,15 +422,15 @@ static size_t make_oggs_page(uint8_t * buf, int buf_size, size_t data_size, int
|
||||
}
|
||||
|
||||
segment_count = (int)(data_size / 0xFF + 1);
|
||||
put_32bitBE(buf+0x00, 0x4F676753); /* capture pattern ("OggS") */
|
||||
put_8bit (buf+0x04, 0); /* stream structure version, fixed */
|
||||
put_8bit (buf+0x05, header_type_flag); /* bitflags (0: normal, continued = 1, first = 2, last = 4) */
|
||||
put_32bitLE(buf+0x06, (uint32_t)(absolute_granule >> 0 & 0xFFFFFFFF)); /* lower */
|
||||
put_32bitLE(buf+0x0A, (uint32_t)(absolute_granule >> 32 & 0xFFFFFFFF)); /* upper */
|
||||
put_32bitLE(buf+0x0E, stream_serial_number); /* for interleaved multi-streams */
|
||||
put_32bitLE(buf+0x12, page_sequence);
|
||||
put_32bitLE(buf+0x16, checksum); /* 0 for now, until all data is written */
|
||||
put_8bit (buf+0x1A, segment_count); /* count of all lacing values */
|
||||
put_u32be(buf+0x00, 0x4F676753); /* capture pattern ("OggS") */
|
||||
put_u8 (buf+0x04, 0); /* stream structure version, fixed */
|
||||
put_u8 (buf+0x05, header_type_flag); /* bitflags (0: normal, continued = 1, first = 2, last = 4) */
|
||||
put_u32le(buf+0x06, (uint32_t)(absolute_granule >> 0 & 0xFFFFFFFF)); /* lower */
|
||||
put_u32le(buf+0x0A, (uint32_t)(absolute_granule >> 32 & 0xFFFFFFFF)); /* upper */
|
||||
put_u32le(buf+0x0E, stream_serial_number); /* for interleaved multi-streams */
|
||||
put_u32le(buf+0x12, page_sequence);
|
||||
put_u32le(buf+0x16, checksum); /* 0 for now, until all data is written */
|
||||
put_u8 (buf+0x1A, segment_count); /* count of all lacing values */
|
||||
|
||||
/* segment table: size N in "lacing values" (ex. 0x20E=0xFF+FF+10; 0xFF=0xFF+00) */
|
||||
page_done = 0x1B;
|
||||
@ -407,12 +439,12 @@ static size_t make_oggs_page(uint8_t * buf, int buf_size, size_t data_size, int
|
||||
if (bytes > 0xFF)
|
||||
bytes = 0xFF;
|
||||
|
||||
put_8bit(buf+page_done, bytes);
|
||||
put_u8(buf+page_done, bytes);
|
||||
page_done++;
|
||||
lacing_done += bytes;
|
||||
|
||||
if (lacing_done == data_size && bytes == 0xFF) {
|
||||
put_8bit(buf+page_done, 0x00);
|
||||
put_u8(buf+page_done, 0x00);
|
||||
page_done++;
|
||||
}
|
||||
}
|
||||
@ -423,14 +455,14 @@ static size_t make_oggs_page(uint8_t * buf, int buf_size, size_t data_size, int
|
||||
|
||||
/* final checksum */
|
||||
checksum = get_oggs_checksum(buf, page_done);
|
||||
put_32bitLE(buf+0x16, checksum);
|
||||
put_u32le(buf+0x16, checksum);
|
||||
|
||||
return page_done;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t make_opus_header(uint8_t * buf, int buf_size, opus_config *cfg) {
|
||||
static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) {
|
||||
size_t header_size = 0x13;
|
||||
int mapping_family = 0;
|
||||
|
||||
@ -446,25 +478,25 @@ static size_t make_opus_header(uint8_t * buf, int buf_size, opus_config *cfg) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
put_32bitBE(buf+0x00, 0x4F707573); /* "Opus" header magic */
|
||||
put_32bitBE(buf+0x04, 0x48656164); /* "Head" header magic */
|
||||
put_8bit (buf+0x08, 1); /* version */
|
||||
put_8bit (buf+0x09, cfg->channels);
|
||||
put_16bitLE(buf+0x0A, cfg->skip);
|
||||
put_32bitLE(buf+0x0c, cfg->sample_rate);
|
||||
put_16bitLE(buf+0x10, 0); /* output gain */
|
||||
put_8bit (buf+0x12, mapping_family);
|
||||
put_u32be(buf+0x00, 0x4F707573); /* "Opus" header magic */
|
||||
put_u32be(buf+0x04, 0x48656164); /* "Head" header magic */
|
||||
put_u8 (buf+0x08, 1); /* version */
|
||||
put_u8 (buf+0x09, cfg->channels);
|
||||
put_s16le(buf+0x0A, cfg->skip);
|
||||
put_u32le(buf+0x0c, cfg->sample_rate);
|
||||
put_u16le(buf+0x10, 0); /* output gain */
|
||||
put_u8 (buf+0x12, mapping_family);
|
||||
|
||||
if (mapping_family > 0) {
|
||||
int i;
|
||||
|
||||
/* internal mono/stereo streams (N mono/stereo streams form M channels) */
|
||||
put_8bit(buf+0x13, cfg->stream_count);
|
||||
put_u8(buf+0x13, cfg->stream_count);
|
||||
/* joint stereo streams (rest would be mono, so 6ch can be 2ch+2ch+1ch+1ch = 2 coupled */
|
||||
put_8bit(buf+0x14, cfg->coupled_count);
|
||||
put_u8(buf+0x14, cfg->coupled_count);
|
||||
/* mapping bits per channel? */
|
||||
for (i = 0; i < cfg->channels; i++) {
|
||||
put_8bit(buf+0x15+i, cfg->channel_mapping[i]);
|
||||
put_u8(buf+0x15+i, cfg->channel_mapping[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -473,9 +505,9 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t make_opus_comment(uint8_t * buf, int buf_size) {
|
||||
const char * vendor_string = "vgmstream";
|
||||
const char * user_comment_0_string = "vgmstream Opus converter";
|
||||
static size_t make_opus_comment(uint8_t* buf, int buf_size) {
|
||||
const char* vendor_string = "vgmstream";
|
||||
const char* user_comment_0_string = "vgmstream Opus converter";
|
||||
size_t comment_size;
|
||||
int vendor_string_length, user_comment_0_length;
|
||||
|
||||
@ -488,20 +520,20 @@ static size_t make_opus_comment(uint8_t * buf, int buf_size) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
put_32bitBE(buf+0x00, 0x4F707573); /* "Opus" header magic */
|
||||
put_32bitBE(buf+0x04, 0x54616773); /* "Tags" header magic */
|
||||
put_32bitLE(buf+0x08, vendor_string_length);
|
||||
memcpy (buf+0x0c, vendor_string, vendor_string_length);
|
||||
put_32bitLE(buf+0x0c + vendor_string_length+0x00, 1); /* user_comment_list_length */
|
||||
put_32bitLE(buf+0x0c + vendor_string_length+0x04, user_comment_0_length);
|
||||
memcpy (buf+0x0c + vendor_string_length+0x08, user_comment_0_string, user_comment_0_length);
|
||||
put_u32be(buf+0x00, 0x4F707573); /* "Opus" header magic */
|
||||
put_u32be(buf+0x04, 0x54616773); /* "Tags" header magic */
|
||||
put_u32le(buf+0x08, vendor_string_length);
|
||||
memcpy (buf+0x0c, vendor_string, vendor_string_length);
|
||||
put_u32le(buf+0x0c + vendor_string_length+0x00, 1); /* user_comment_list_length */
|
||||
put_u32le(buf+0x0c + vendor_string_length+0x04, user_comment_0_length);
|
||||
memcpy (buf+0x0c + vendor_string_length+0x08, user_comment_0_string, user_comment_0_length);
|
||||
|
||||
return comment_size;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t make_oggs_first(uint8_t * buf, int buf_size, opus_config *cfg) {
|
||||
static size_t make_oggs_first(uint8_t* buf, int buf_size, opus_config* cfg) {
|
||||
int buf_done = 0;
|
||||
size_t bytes;
|
||||
|
||||
@ -523,7 +555,7 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t opus_get_packet_samples(const uint8_t * buf, int len) {
|
||||
static size_t opus_get_packet_samples(const uint8_t* buf, int len) {
|
||||
return opus_packet_get_nb_frames(buf, len) * opus_packet_get_samples_per_frame(buf, 48000);
|
||||
}
|
||||
static size_t opus_get_packet_samples_sf(STREAMFILE* sf, off_t offset) {
|
||||
@ -534,11 +566,15 @@ static size_t opus_get_packet_samples_sf(STREAMFILE* sf, off_t offset) {
|
||||
|
||||
/************************** */
|
||||
|
||||
static size_t get_xopus_packet_size(int packet, STREAMFILE* sf) {
|
||||
/* XOPUS has a packet size table at the beginning, get size from there.
|
||||
* Maybe should copy the table during setup to avoid IO, but all XOPUS are
|
||||
* quite small so it isn't very noticeable. */
|
||||
return read_u16le(0x20 + packet*0x02, sf);
|
||||
/* some formats store all frames in a table, rather than right before the frame */
|
||||
static size_t get_table_frame_size(opus_io_data* data, int frame) {
|
||||
if (frame < 0 || frame >= data->table_count) {
|
||||
VGM_LOG("OPUS: wrong requested frame %i, count=%i\n", frame, data->table_count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//;VGM_LOG("OPUS: frame %i size=%x\n", frame, data->frame_table[frame]);
|
||||
return data->frame_table[frame];
|
||||
}
|
||||
|
||||
|
||||
@ -575,10 +611,14 @@ static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFI
|
||||
data_size = read_u16be(offset, sf);
|
||||
skip_size = 0x02;
|
||||
break;
|
||||
|
||||
#if 0 // needs data* for frame table, but num_samples should exist on header
|
||||
case OPUS_X:
|
||||
data_size = get_xopus_packet_size(packet, sf);
|
||||
case OPUS_WWISE:
|
||||
data_size = get_table_frame_size(data, packet);
|
||||
skip_size = 0x00;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@ -618,6 +658,7 @@ static size_t custom_opus_get_encoder_delay(off_t offset, STREAMFILE* sf, opus_t
|
||||
skip_size = 0x02;
|
||||
break;
|
||||
case OPUS_X:
|
||||
case OPUS_WWISE:
|
||||
skip_size = 0x00;
|
||||
break;
|
||||
default:
|
||||
@ -645,7 +686,7 @@ size_t fsb_opus_get_encoder_delay(off_t offset, STREAMFILE* sf) {
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
/* actual FFmpeg only-code starts here (the above is universal enough but no point to compile) */
|
||||
/* actual FFmpeg only-code starts here (the above is universal enough but no point to compile separatedly) */
|
||||
//#ifdef VGM_USE_FFMPEG
|
||||
|
||||
static ffmpeg_codec_data* init_ffmpeg_custom_opus_config(STREAMFILE* sf, off_t start_offset, size_t data_size, opus_config *cfg, opus_type_t type) {
|
||||
@ -672,6 +713,7 @@ fail:
|
||||
close_streamfile(temp_sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ffmpeg_codec_data* init_ffmpeg_custom_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate, opus_type_t type) {
|
||||
opus_config cfg = {0};
|
||||
cfg.channels = channels;
|
||||
@ -681,6 +723,18 @@ static ffmpeg_codec_data* init_ffmpeg_custom_opus(STREAMFILE* sf, off_t start_of
|
||||
return init_ffmpeg_custom_opus_config(sf, start_offset, data_size, &cfg, type);
|
||||
}
|
||||
|
||||
ffmpeg_codec_data* init_ffmpeg_custom_table_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip, int sample_rate, opus_type_t type) {
|
||||
opus_config cfg = {0};
|
||||
cfg.channels = channels;
|
||||
cfg.skip = skip;
|
||||
cfg.sample_rate = sample_rate;
|
||||
cfg.table_offset = table_offset;
|
||||
cfg.table_count = table_count;
|
||||
|
||||
return init_ffmpeg_custom_opus_config(sf, data_offset, data_size, &cfg, type);
|
||||
}
|
||||
|
||||
|
||||
ffmpeg_codec_data* init_ffmpeg_switch_opus_config(STREAMFILE* sf, off_t start_offset, size_t data_size, opus_config* cfg) {
|
||||
return init_ffmpeg_custom_opus_config(sf, start_offset, data_size, cfg, OPUS_SWITCH);
|
||||
}
|
||||
@ -693,12 +747,15 @@ ffmpeg_codec_data* init_ffmpeg_ue4_opus(STREAMFILE* sf, off_t start_offset, size
|
||||
ffmpeg_codec_data* init_ffmpeg_ea_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
|
||||
return init_ffmpeg_custom_opus(sf, start_offset, data_size, channels, skip, sample_rate, OPUS_EA);
|
||||
}
|
||||
ffmpeg_codec_data* init_ffmpeg_x_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
|
||||
return init_ffmpeg_custom_opus(sf, start_offset, data_size, channels, skip, sample_rate, OPUS_X);
|
||||
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) {
|
||||
return init_ffmpeg_custom_table_opus(sf, table_offset, table_count, data_offset, data_size, channels, skip, 0, OPUS_X);
|
||||
}
|
||||
ffmpeg_codec_data* init_ffmpeg_fsb_opus(STREAMFILE* sf, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate) {
|
||||
return init_ffmpeg_custom_opus(sf, start_offset, data_size, channels, skip, sample_rate, OPUS_FSB);
|
||||
}
|
||||
ffmpeg_codec_data* init_ffmpeg_wwise_opus(STREAMFILE* sf, off_t table_offset, int table_count, off_t data_offset, size_t data_size, int channels, int skip) {
|
||||
return init_ffmpeg_custom_table_opus(sf, table_offset, table_count, data_offset, data_size, channels, skip, 0, OPUS_WWISE);
|
||||
}
|
||||
|
||||
static opus_type_t get_ue4opus_version(STREAMFILE* sf, off_t offset) {
|
||||
int read_samples, calc_samples;
|
||||
|
@ -1314,6 +1314,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_SDRH, "feelplus SDRH header"},
|
||||
{meta_WADY, "Marble WADY header"},
|
||||
{meta_DSP_SQEX, "Square Enix DSP header"},
|
||||
{meta_DSP_WIIVOICE, "Koei Tecmo WiiVoice header"},
|
||||
};
|
||||
|
||||
void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) {
|
||||
|
@ -180,10 +180,11 @@ static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstre
|
||||
|
||||
/* try parsing TXTM if present */
|
||||
sf_acb = read_filemap_file(sf_acb, 0);
|
||||
if (sf_acb) return sf_acb;
|
||||
|
||||
/* try (name).awb + (name).awb */
|
||||
sf_acb = open_streamfile_by_ext(sf, "acb");
|
||||
if (!sf_acb) {
|
||||
sf_acb = open_streamfile_by_ext(sf, "acb");
|
||||
}
|
||||
|
||||
/* try (name)_streamfiles.awb + (name).acb */
|
||||
if (!sf_acb) {
|
||||
|
@ -802,11 +802,11 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* sf_mus = NULL;
|
||||
segmented_layout_data *data_s = NULL;
|
||||
uint32_t track_start, track_end, track_hash, tracks_table, samples_table, section_offset, entry_offset, eof_offset, off_mult, sound_offset;
|
||||
uint32_t track_start, track_end = 0, track_hash = 0, tracks_table, samples_table = 0, section_offset, entry_offset = 0, eof_offset = 0, off_mult, sound_offset;
|
||||
uint16_t num_nodes;
|
||||
uint8_t version, sub_version, num_tracks, num_sections, num_events, num_routers, num_vars, subentry_num;
|
||||
int32_t(*read_u32)(off_t, STREAMFILE*);
|
||||
int16_t(*read_u16)(off_t, STREAMFILE*);
|
||||
uint8_t version, sub_version, num_tracks, num_sections, num_events, num_routers, num_vars, subentry_num = 0;
|
||||
uint32_t(*read_u32)(off_t, STREAMFILE*);
|
||||
uint16_t(*read_u16)(off_t, STREAMFILE*);
|
||||
int i;
|
||||
int target_stream = sf->stream_index, total_streams, big_endian, is_bnk = 0;
|
||||
|
||||
@ -997,7 +997,7 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE* sf) {
|
||||
if (version == 5 && bnk_index != 0) {
|
||||
/* HACK: open proper .mus now since open_mapfile_pair doesn't let us adjust the name */
|
||||
char filename[PATH_LIMIT], basename[PATH_LIMIT], ext[32];
|
||||
int basename_len, fileext_len;
|
||||
int basename_len;
|
||||
|
||||
get_streamfile_basename(sf_mus, basename, PATH_LIMIT);
|
||||
basename_len = strlen(basename);
|
||||
|
262
src/meta/kwb.c
262
src/meta/kwb.c
@ -1,9 +1,10 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
typedef enum { PCM16, MSADPCM, DSP, AT9 } kwb_codec;
|
||||
typedef enum { PCM16, MSADPCM, DSP_HEAD, DSP_BODY, AT9, MSF } kwb_codec;
|
||||
|
||||
typedef struct {
|
||||
int big_endian;
|
||||
int total_subsongs;
|
||||
int target_subsong;
|
||||
kwb_codec codec;
|
||||
@ -24,13 +25,15 @@ typedef struct {
|
||||
} kwb_header;
|
||||
|
||||
static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b);
|
||||
static int parse_xws(kwb_header* kwb, STREAMFILE* sf);
|
||||
|
||||
|
||||
/* KWB - WaveBank from Koei games */
|
||||
VGMSTREAM * init_vgmstream_kwb(STREAMFILE* sf) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
VGMSTREAM* init_vgmstream_kwb(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE *sf_h = NULL, *sf_b = NULL;
|
||||
kwb_header kwb = {0};
|
||||
int32_t (*read_s32)(off_t,STREAMFILE*) = NULL;
|
||||
int target_subsong = sf->stream_index;
|
||||
|
||||
|
||||
@ -67,6 +70,7 @@ VGMSTREAM * init_vgmstream_kwb(STREAMFILE* sf) {
|
||||
|
||||
if (!parse_kwb(&kwb, sf_h, sf_b))
|
||||
goto fail;
|
||||
read_s32 = kwb.big_endian ? read_s32be : read_s32le;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
@ -92,13 +96,24 @@ VGMSTREAM * init_vgmstream_kwb(STREAMFILE* sf) {
|
||||
vgmstream->frame_size = kwb.block_size;
|
||||
break;
|
||||
|
||||
case DSP:
|
||||
case DSP_HEAD:
|
||||
case DSP_BODY:
|
||||
if (kwb.channels > 1) goto fail;
|
||||
vgmstream->coding_type = coding_NGC_DSP; /* subinterleave? */
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->layout_type = 0x08;
|
||||
dsp_read_coefs_le(vgmstream, sf_h, kwb.dsp_offset + 0x1c, 0x60);
|
||||
dsp_read_hist_le (vgmstream, sf_h, kwb.dsp_offset + 0x40, 0x60);
|
||||
vgmstream->interleave_block_size = 0x08;
|
||||
if (kwb.codec == DSP_HEAD) {
|
||||
dsp_read_coefs(vgmstream, sf_h, kwb.dsp_offset + 0x1c, 0x60, kwb.big_endian);
|
||||
dsp_read_hist (vgmstream, sf_h, kwb.dsp_offset + 0x40, 0x60, kwb.big_endian);
|
||||
}
|
||||
else {
|
||||
/* typical DSP header + data */
|
||||
vgmstream->num_samples = read_s32(kwb.stream_offset + 0x00, sf_b);
|
||||
dsp_read_coefs(vgmstream, sf_b, kwb.stream_offset + 0x1c, 0x60, kwb.big_endian);
|
||||
dsp_read_hist (vgmstream, sf_b, kwb.stream_offset + 0x40, 0x60, kwb.big_endian);
|
||||
kwb.stream_offset += 0x60;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
@ -130,8 +145,6 @@ VGMSTREAM * init_vgmstream_kwb(STREAMFILE* sf) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
@ -148,6 +161,54 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* XWS - WaveStream? from Koei games */
|
||||
VGMSTREAM* init_vgmstream_xws(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
kwb_header kwb = {0};
|
||||
int target_subsong = sf->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "xws"))
|
||||
goto fail;
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
kwb.target_subsong = target_subsong;
|
||||
|
||||
if (!parse_xws(&kwb, sf))
|
||||
goto fail;
|
||||
|
||||
if (kwb.codec == MSF) {
|
||||
if (kwb.stream_offset == 0) {
|
||||
vgmstream = init_vgmstream_silence(0,0,0); /* dummy, whatevs */
|
||||
if (!vgmstream) goto fail;
|
||||
}
|
||||
else {
|
||||
kwb.stream_size = read_u32be(kwb.stream_offset + 0x0c, sf) + 0x40;
|
||||
|
||||
temp_sf = setup_subfile_streamfile(sf, kwb.stream_offset, kwb.stream_size, "msf");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_msf(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_streams = kwb.total_subsongs;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int parse_type_kwb2(kwb_header* kwb, off_t offset, STREAMFILE* sf_h) {
|
||||
int i, j, sounds;
|
||||
|
||||
@ -220,7 +281,7 @@ static int parse_type_kwb2(kwb_header* kwb, off_t offset, STREAMFILE* sf_h) {
|
||||
kwb->codec = MSADPCM;
|
||||
break;
|
||||
case 0x90:
|
||||
kwb->codec = DSP;
|
||||
kwb->codec = DSP_HEAD;
|
||||
kwb->dsp_offset = subsound_offset + 0x4c;
|
||||
break;
|
||||
default:
|
||||
@ -257,8 +318,8 @@ static int parse_type_k4hd(kwb_header* kwb, off_t offset, STREAMFILE* sf_h) {
|
||||
|
||||
|
||||
/* a format mimicking PSVita's hd4+bd4 format */
|
||||
/* 00 K4HD id */
|
||||
/* 04 chunk size */
|
||||
/* 00: K4HD id */
|
||||
/* 04: chunk size */
|
||||
/* 08: ? */
|
||||
/* 0c: ? */
|
||||
/* 10: PPPG offset ('program'? cues?) */
|
||||
@ -271,7 +332,7 @@ static int parse_type_k4hd(kwb_header* kwb, off_t offset, STREAMFILE* sf_h) {
|
||||
if (read_u32be(ppva_offset + 0x00, sf_h) != 0x50505641) /* "PPVA" */
|
||||
goto fail;
|
||||
|
||||
entry_size = read_u32le(ppva_offset + 0x08, sf_h); /* */
|
||||
entry_size = read_u32le(ppva_offset + 0x08, sf_h);
|
||||
/* 0x0c: -1? */
|
||||
/* 0x10: 0? */
|
||||
entries = read_u32le(ppva_offset + 0x14, sf_h) + 1;
|
||||
@ -314,16 +375,78 @@ static int parse_type_sdsd(kwb_header* kwb, off_t offset, STREAMFILE* sf_h) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_type_sdwi(kwb_header* kwb, off_t offset, STREAMFILE* sf_h) {
|
||||
off_t smpl_offset, header_offset;
|
||||
int entries;
|
||||
size_t entry_size;
|
||||
|
||||
|
||||
/* variation of SDsd */
|
||||
/* 00: SDWiVers */
|
||||
/* 08: chunk size */
|
||||
/* 0c: null */
|
||||
/* 10: SDsdHead */
|
||||
/* 18: chunk size */
|
||||
/* 1c: WBH_ size */
|
||||
/* 20: WBD_ size */
|
||||
/* 24: SDsdProg offset ('program'? cues?) */
|
||||
/* 28: SDsdSmpl offset ('samples'? waves?) */
|
||||
/* rest: ? */
|
||||
smpl_offset = read_u32be(offset + 0x28, sf_h);
|
||||
smpl_offset += offset;
|
||||
|
||||
/* Smpl table: */
|
||||
if (read_u32be(smpl_offset + 0x00, sf_h) != 0x53447364 && /* "SDsd" */
|
||||
read_u32be(smpl_offset + 0x04, sf_h) != 0x536D706C) /* "Smpl" */
|
||||
goto fail;
|
||||
|
||||
/* 0x08: ? */
|
||||
entries = read_u32le(smpl_offset + 0x0c, sf_h); /* LE! */
|
||||
entry_size = 0x40;
|
||||
|
||||
kwb->total_subsongs = entries;
|
||||
if (kwb->target_subsong < 0 || kwb->target_subsong > kwb->total_subsongs || kwb->total_subsongs < 1) goto fail;
|
||||
|
||||
header_offset = smpl_offset + 0x10 + (kwb->target_subsong-1) * entry_size;
|
||||
|
||||
/* 00: "SS" + ID (0..N) */
|
||||
kwb->stream_offset = read_u32be(header_offset + 0x04, sf_h);
|
||||
/* 08: flag? */
|
||||
/* 0c: ? + channels? */
|
||||
kwb->sample_rate = read_u32be(header_offset + 0x10, sf_h);
|
||||
/* 14: bitrate */
|
||||
/* 18: codec? + bps */
|
||||
/* 1c: null? */
|
||||
/* 20: null? */
|
||||
kwb->stream_size = read_u32be(header_offset + 0x24, sf_h);
|
||||
/* 28: full stream size (with padding) */
|
||||
/* 2c: related to samples? */
|
||||
/* 30: ID */
|
||||
/* 34-38: null */
|
||||
|
||||
kwb->codec = DSP_BODY;
|
||||
kwb->channels = 1;
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
|
||||
off_t head_offset, body_offset, start;
|
||||
uint32_t type;
|
||||
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
|
||||
|
||||
|
||||
if (read_u32be(0x00, sf_h) == 0x57484431) { /* "WHD1" */
|
||||
/* container of wbh+wbd */
|
||||
/* container of fused .wbh+wbd */
|
||||
/* 0x04: fixed value? */
|
||||
/* 0x08: version? */
|
||||
start = read_u32le(0x0c, sf_h);
|
||||
kwb->big_endian = read_u8(0x08, sf_h) == 0xFF;
|
||||
/* 0x0a: version? */
|
||||
|
||||
read_u32 = kwb->big_endian ? read_u32be : read_u32le;
|
||||
|
||||
start = read_u32(0x0c, sf_h);
|
||||
/* 0x10: file size */
|
||||
/* 0x14: subfiles? */
|
||||
/* 0x18: subfiles? */
|
||||
@ -331,8 +454,8 @@ static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
|
||||
/* 0x20: some size? */
|
||||
/* 0x24: some size? */
|
||||
|
||||
head_offset = read_u32le(start + 0x00, sf_h);
|
||||
body_offset = read_u32le(start + 0x04, sf_h);
|
||||
head_offset = read_u32(start + 0x00, sf_h);
|
||||
body_offset = read_u32(start + 0x04, sf_h);
|
||||
/* 0x10: head size */
|
||||
/* 0x14: body size */
|
||||
}
|
||||
@ -340,13 +463,17 @@ static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
|
||||
/* dual file */
|
||||
head_offset = 0x00;
|
||||
body_offset = 0x00;
|
||||
|
||||
kwb->big_endian = guess_endianness32bit(head_offset + 0x08, sf_h);
|
||||
|
||||
read_u32 = kwb->big_endian ? read_u32be : read_u32le;
|
||||
}
|
||||
|
||||
if (read_u32be(head_offset + 0x00, sf_h) != 0x5F484257 || /* "_HBW" */
|
||||
read_u32be(head_offset + 0x04, sf_h) != 0x30303030) /* "0000" */
|
||||
if (read_u32(head_offset + 0x00, sf_h) != 0x5742485F || /* "WBH_" */
|
||||
read_u32(head_offset + 0x04, sf_h) != 0x30303030) /* "0000" */
|
||||
goto fail;
|
||||
if (read_u32be(body_offset + 0x00, sf_b) != 0x5F444257 || /* "_DBW" */
|
||||
read_u32be(body_offset + 0x04, sf_b) != 0x30303030) /* "0000" */
|
||||
if (read_u32(body_offset + 0x00, sf_b) != 0x5742445F || /* "WBD_" */
|
||||
read_u32(body_offset + 0x04, sf_b) != 0x30303030) /* "0000" */
|
||||
goto fail;
|
||||
/* 0x08: head/body size */
|
||||
|
||||
@ -356,26 +483,109 @@ static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
|
||||
/* format has multiple bank subtypes that are quite different from each other */
|
||||
type = read_u32be(head_offset + 0x00, sf_h);
|
||||
switch(type) {
|
||||
case 0x4B574232: /* "KWB2" (PC) */
|
||||
case 0x4B57424E: /* "KWBN" (Switch) */
|
||||
case 0x4B574232: /* "KWB2" [Bladestorm Nightmare (PC), Dissidia NT (PC)] */
|
||||
case 0x4B57424E: /* "KWBN" [Fire Emblem Warriors (Switch)] */
|
||||
if (!parse_type_kwb2(kwb, head_offset, sf_h))
|
||||
goto fail;
|
||||
break;
|
||||
|
||||
case 0x4B344844: /* "K4HD" (PS4/Vita) */
|
||||
case 0x4B344844: /* "K4HD" [Dissidia NT (PS4), (Vita) */
|
||||
if (!parse_type_k4hd(kwb, head_offset, sf_h))
|
||||
goto fail;
|
||||
break;
|
||||
|
||||
case 0x53447364: /* "SDsd" (PS3?) */
|
||||
case 0x53447364: /* "SDsd" (PS3? leftover files) */
|
||||
if (!parse_type_sdsd(kwb, head_offset, sf_h))
|
||||
goto fail;
|
||||
break;
|
||||
|
||||
case 0x53445769: /* "SDWi" [Fatal Frame 5 (WiiU)] */
|
||||
if (!parse_type_sdwi(kwb, head_offset, sf_h))
|
||||
goto fail;
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
kwb->stream_offset += body_offset;
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_type_msfbank(kwb_header* kwb, off_t offset, STREAMFILE* sf) {
|
||||
/* this is just like XWSF, abridged: */
|
||||
off_t header_offset;
|
||||
|
||||
kwb->total_subsongs = read_u32be(offset + 0x14, sf);
|
||||
if (kwb->target_subsong < 0 || kwb->target_subsong > kwb->total_subsongs || kwb->total_subsongs < 1) goto fail;
|
||||
|
||||
header_offset = offset + 0x30 + (kwb->target_subsong-1) * 0x04;
|
||||
|
||||
/* just a dumb table pointing to MSF, entries can be dummy */
|
||||
kwb->stream_offset = read_u32be(header_offset, sf);
|
||||
kwb->codec = MSF;
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int parse_xws(kwb_header* kwb, STREAMFILE* sf) {
|
||||
off_t head_offset, body_offset, start;
|
||||
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
|
||||
int chunks, chunks2;
|
||||
off_t msfb_offset;
|
||||
|
||||
/* format is similar to WHD1 with some annoyances of its own
|
||||
* variations:
|
||||
* - tdpack: points to N XWSFILE
|
||||
* - XWSFILE w/ 4 chunks: CUEBANK offset, ? offset, MSFBANK offset, end offset (PS3)
|
||||
* [Ninja Gaiden Sigma 2 (PS3), Ninja Gaiden 3 Razor's Edge (PS3)]
|
||||
* - XWSFILE w/ 2*N chunks: KWB2 offset + data offset * N (ex. 3 pairs = 6 chunks)
|
||||
* [Dead or Alive 5 Last Round (PC)]
|
||||
*
|
||||
* for now basic support for the second case, others we'd have to map subsong N to internal bank M
|
||||
*/
|
||||
|
||||
if (read_u32be(0x00, sf) != 0x58575346 || /* "XWSF" */
|
||||
read_u32be(0x04, sf) != 0x494C4500) /* "ILE\0" */
|
||||
goto fail;
|
||||
|
||||
kwb->big_endian = read_u8(0x08, sf) == 0xFF;
|
||||
/* 0x0a: version? */
|
||||
|
||||
read_u32 = kwb->big_endian ? read_u32be : read_u32le;
|
||||
|
||||
start = read_u32(0x0c, sf);
|
||||
/* 0x10: file size */
|
||||
chunks = read_u32(0x14, sf);
|
||||
chunks2 = read_u32(0x18, sf);
|
||||
/* 0x1c: null */
|
||||
/* 0x20: some size? */
|
||||
/* 0x24: some size? */
|
||||
if (chunks != chunks2)
|
||||
goto fail;
|
||||
|
||||
if (chunks != 4)
|
||||
goto fail;
|
||||
|
||||
msfb_offset = read_u32(start + 0x08, sf);
|
||||
if (read_u32be(msfb_offset, sf) == 0x4D534642) { /* "MSFB" + "ANK\0" */
|
||||
head_offset = msfb_offset;
|
||||
body_offset = msfb_offset; /* relative to start */
|
||||
|
||||
if (!parse_type_msfbank(kwb, head_offset, sf))
|
||||
goto fail;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
kwb->stream_offset += body_offset;
|
||||
|
||||
return 1;
|
||||
|
@ -54,7 +54,8 @@ VGMSTREAM* init_vgmstream_dsp_adpy(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_dsp_adpx(STREAMFILE* sf);
|
||||
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_sqex(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_dsp_wiivoice(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_csmp(STREAMFILE *streamFile);
|
||||
|
||||
@ -886,7 +887,8 @@ VGMSTREAM* init_vgmstream_fda(STREAMFILE *sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_tgc(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_kwb(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_kwb(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_xws(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_lrmd(STREAMFILE* sf);
|
||||
|
||||
|
@ -1278,3 +1278,31 @@ VGMSTREAM* init_vgmstream_dsp_sqex(STREAMFILE* sf) {
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* WiiVoice - Koei Tecmo wrapper [Fatal Frame 5 (WiiU)] */
|
||||
VGMSTREAM* init_vgmstream_dsp_wiivoice(STREAMFILE* sf) {
|
||||
dsp_meta dspm = {0};
|
||||
/* also see g1l.c for WiiBGM weirder variation */
|
||||
|
||||
/* checks */
|
||||
/* .dsp: assumed */
|
||||
if (!check_extensions(sf, "dsp"))
|
||||
goto fail;
|
||||
if (read_u32be(0x00,sf) != 0x57696956 && /* "WiiV" */
|
||||
read_u32be(0x04,sf) != 0x6F696365) /* "oice" */
|
||||
goto fail;
|
||||
|
||||
dspm.channel_count = 1;
|
||||
dspm.max_channels = 1;
|
||||
|
||||
dspm.header_offset = read_u32be(0x08,sf);
|
||||
/* 0x10: file size */
|
||||
/* 0x14: data size */
|
||||
dspm.header_spacing = 0x60;
|
||||
dspm.start_offset = dspm.header_offset + dspm.header_spacing*dspm.channel_count;
|
||||
|
||||
dspm.meta_type = meta_DSP_WIIVOICE;
|
||||
return init_vgmstream_dsp_common(sf, &dspm);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
@ -412,6 +412,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
|
||||
|
||||
else if (codec == 0x0011 && (riff_size / 2 / 2 == read_32bitLE(0x30,sf))) /* riff_size = pcm_size (always stereo, has fact at 0x30) */
|
||||
riff_size = file_size - 0x08; /* [Asphalt 6 (iOS)] (sfx/memory wavs have ok sizes?) */
|
||||
|
||||
else if (codec == 0xFFFE && riff_size + 0x08 + 0x30 == file_size)
|
||||
riff_size += 0x30; /* [E.X. Troopers (PS3)] (adds "ver /eBIT/tIME/mrkr" empty chunks but RIFF size wasn't updated) */
|
||||
}
|
||||
|
||||
/* check for truncated RIFF */
|
||||
|
@ -95,7 +95,7 @@ typedef struct {
|
||||
|
||||
int target_subsong;
|
||||
uint32_t subsong_count;
|
||||
uint32_t subsong_offset;
|
||||
uint32_t subsong_spacing;
|
||||
|
||||
uint32_t name_offset_set;
|
||||
uint32_t name_offset;
|
||||
@ -1017,7 +1017,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
|
||||
txth->num_samples = get_bytes_to_samples(txth, txth->num_samples * (txth->interleave*txth->channels));
|
||||
}
|
||||
}
|
||||
else if (is_string(key,"loop_start_sample")) {
|
||||
else if (is_string(key,"loop_start_sample") || is_string(key,"loop_start")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->loop_start_sample)) goto fail;
|
||||
if (txth->sample_type==1)
|
||||
txth->loop_start_sample = get_bytes_to_samples(txth, txth->loop_start_sample);
|
||||
@ -1026,7 +1026,7 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
|
||||
if (txth->loop_adjust)
|
||||
txth->loop_start_sample += txth->loop_adjust;
|
||||
}
|
||||
else if (is_string(key,"loop_end_sample")) {
|
||||
else if (is_string(key,"loop_end_sample") || is_string(key,"loop_end")) {
|
||||
if (is_string(val,"data_size")) {
|
||||
txth->loop_end_sample = get_bytes_to_samples(txth, txth->data_size);
|
||||
}
|
||||
@ -1098,8 +1098,8 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->coef_offset)) goto fail;
|
||||
/* special adjustments */
|
||||
txth->coef_offset += txth->base_offset;
|
||||
if (txth->subsong_offset)
|
||||
txth->coef_offset += txth->subsong_offset * (txth->target_subsong - 1);
|
||||
if (txth->subsong_spacing)
|
||||
txth->coef_offset += txth->subsong_spacing * (txth->target_subsong - 1);
|
||||
}
|
||||
else if (is_string(key,"coef_spacing")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->coef_spacing)) goto fail;
|
||||
@ -1125,8 +1125,8 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
|
||||
txth->hist_set = 1;
|
||||
/* special adjustment */
|
||||
txth->hist_offset += txth->hist_offset;
|
||||
if (txth->subsong_offset)
|
||||
txth->hist_offset += txth->subsong_offset * (txth->target_subsong - 1);
|
||||
if (txth->subsong_spacing)
|
||||
txth->hist_offset += txth->subsong_spacing * (txth->target_subsong - 1);
|
||||
}
|
||||
else if (is_string(key,"hist_spacing")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->hist_spacing)) goto fail;
|
||||
@ -1143,16 +1143,23 @@ static int parse_keyval(STREAMFILE* sf_, txth_header* txth, const char * key, ch
|
||||
else if (is_string(key,"subsong_count")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->subsong_count)) goto fail;
|
||||
}
|
||||
else if (is_string(key,"subsong_offset")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->subsong_offset)) goto fail;
|
||||
else if (is_string(key,"subsong_spacing") || is_string(key,"subsong_offset")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->subsong_spacing)) goto fail;
|
||||
}
|
||||
else if (is_string(key,"name_offset")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->name_offset)) goto fail;
|
||||
txth->name_offset_set = 1;
|
||||
/* special adjustment */
|
||||
txth->name_offset += txth->base_offset;
|
||||
if (txth->subsong_offset)
|
||||
txth->name_offset += txth->subsong_offset * (txth->target_subsong - 1);
|
||||
if (txth->subsong_spacing)
|
||||
txth->name_offset += txth->subsong_spacing * (txth->target_subsong - 1);
|
||||
}
|
||||
else if (is_string(key,"name_offset_absolute")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->name_offset)) goto fail;
|
||||
txth->name_offset_set = 1;
|
||||
/* special adjustment */
|
||||
txth->name_offset += txth->base_offset;
|
||||
/* unlike the above this is meant for reads that point to somewhere in the file, regardless subsong number */
|
||||
}
|
||||
else if (is_string(key,"name_size")) {
|
||||
if (!parse_num(txth->sf_head,txth,val, &txth->name_size)) goto fail;
|
||||
@ -1561,7 +1568,7 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32
|
||||
uint32_t value_div = txth->value_div;
|
||||
uint32_t value_add = txth->value_add;
|
||||
uint32_t value_sub = txth->value_sub;
|
||||
uint32_t subsong_offset = txth->subsong_offset;
|
||||
uint32_t subsong_spacing = txth->subsong_spacing;
|
||||
|
||||
char op = ' ';
|
||||
int brackets = 0;
|
||||
@ -1626,8 +1633,8 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32
|
||||
else if (!(ed1 == 'L' && ed2 == 'E'))
|
||||
goto fail;
|
||||
|
||||
if (subsong_offset)
|
||||
offset = offset + subsong_offset * (txth->target_subsong - 1);
|
||||
if (subsong_spacing)
|
||||
offset = offset + subsong_spacing * (txth->target_subsong - 1);
|
||||
|
||||
switch(size) {
|
||||
case 1: value = (uint8_t)read_8bit(offset,sf); break;
|
||||
@ -1654,9 +1661,12 @@ static int parse_num(STREAMFILE* sf, txth_header* txth, const char * val, uint32
|
||||
else if ((n = is_string_field(val,"data_size"))) value = txth->data_size;
|
||||
else if ((n = is_string_field(val,"num_samples"))) value = txth->num_samples;
|
||||
else if ((n = is_string_field(val,"loop_start_sample"))) value = txth->loop_start_sample;
|
||||
else if ((n = is_string_field(val,"loop_start"))) value = txth->loop_start_sample;
|
||||
else if ((n = is_string_field(val,"loop_end_sample"))) value = txth->loop_end_sample;
|
||||
else if ((n = is_string_field(val,"loop_end"))) value = txth->loop_end_sample;
|
||||
else if ((n = is_string_field(val,"subsong_count"))) value = txth->subsong_count;
|
||||
else if ((n = is_string_field(val,"subsong_offset"))) value = txth->subsong_offset;
|
||||
else if ((n = is_string_field(val,"subsong_spacing"))) value = txth->subsong_spacing;
|
||||
else if ((n = is_string_field(val,"subsong_offset"))) value = txth->subsong_spacing;
|
||||
else if ((n = is_string_field(val,"subfile_offset"))) value = txth->subfile_offset;
|
||||
else if ((n = is_string_field(val,"subfile_size"))) value = txth->subfile_size;
|
||||
else if ((n = is_string_field(val,"base_offset"))) value = txth->base_offset;
|
||||
|
604
src/meta/wwise.c
604
src/meta/wwise.c
@ -9,18 +9,28 @@
|
||||
* Some info: https://www.audiokinetic.com/en/library/edge/
|
||||
* .bnk (dynamic music/loop) info: https://github.com/bnnm/wwiser
|
||||
*/
|
||||
typedef enum { PCM, IMA, VORBIS, DSP, XMA2, XWMA, AAC, HEVAG, ATRAC9, OPUSNX, OPUS, PTADPCM } wwise_codec;
|
||||
typedef enum { PCM, IMA, VORBIS, DSP, XMA2, XWMA, AAC, HEVAG, ATRAC9, OPUSNX, OPUS, OPUSWW, PTADPCM } wwise_codec;
|
||||
typedef struct {
|
||||
int big_endian;
|
||||
size_t file_size;
|
||||
int truncated;
|
||||
|
||||
/* chunks references */
|
||||
off_t fmt_offset;
|
||||
off_t fmt_offset;
|
||||
size_t fmt_size;
|
||||
off_t data_offset;
|
||||
off_t data_offset;
|
||||
size_t data_size;
|
||||
off_t chunk_offset;
|
||||
off_t xma2_offset;
|
||||
size_t xma2_size;
|
||||
off_t vorb_offset;
|
||||
size_t vorb_size;
|
||||
off_t wiih_offset;
|
||||
size_t wiih_size;
|
||||
off_t smpl_offset;
|
||||
size_t smpl_size;
|
||||
off_t seek_offset;
|
||||
size_t seek_size;
|
||||
|
||||
|
||||
/* standard fmt stuff */
|
||||
wwise_codec codec;
|
||||
@ -39,16 +49,19 @@ typedef struct {
|
||||
int32_t loop_end_sample;
|
||||
} wwise_header;
|
||||
|
||||
static int parse_wwise(STREAMFILE* sf, wwise_header* ww);
|
||||
static int is_dsp_full_interleave(STREAMFILE* sf, wwise_header* ww, off_t coef_offset);
|
||||
|
||||
|
||||
/* Wwise - Audiokinetic Wwise (Wave Works Interactive Sound Engine) middleware */
|
||||
VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
wwise_header ww = {0};
|
||||
off_t start_offset, first_offset = 0xc;
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
||||
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
|
||||
off_t start_offset;
|
||||
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
|
||||
int32_t (*read_s32)(off_t,STREAMFILE*) = NULL;
|
||||
uint16_t (*read_u16)(off_t,STREAMFILE*) = NULL;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .wem: newer "Wwise Encoded Media" used after the 2011.2 SDK (~july 2011)
|
||||
@ -59,198 +72,27 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
if (!check_extensions(sf,"wem,wav,lwav,ogg,logg,xma,bnk"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,sf) != 0x52494646 && /* "RIFF" (LE) */
|
||||
read_32bitBE(0x00,sf) != 0x52494658) /* "RIFX" (BE) */
|
||||
goto fail;
|
||||
if (read_32bitBE(0x08,sf) != 0x57415645 && /* "WAVE" */
|
||||
read_32bitBE(0x08,sf) != 0x58574D41) /* "XWMA" */
|
||||
if (!parse_wwise(sf, &ww))
|
||||
goto fail;
|
||||
|
||||
ww.big_endian = read_32bitBE(0x00,sf) == 0x52494658; /* RIFX */
|
||||
if (ww.big_endian) { /* Wwise honors machine's endianness (PC=RIFF, X360=RIFX --unlike XMA) */
|
||||
read_32bit = read_32bitBE;
|
||||
read_16bit = read_16bitBE;
|
||||
} else {
|
||||
read_32bit = read_32bitLE;
|
||||
read_16bit = read_16bitLE;
|
||||
}
|
||||
|
||||
ww.file_size = sf->get_size(sf);
|
||||
|
||||
#if 0
|
||||
/* Wwise's RIFF size is often wonky, seemingly depending on codec:
|
||||
* - PCM, IMA/PTADPCM, VORBIS, AAC, OPUSNX/OPUS: correct
|
||||
* - DSP, XWMA, ATRAC9: almost always slightly smaller (around 0x50)
|
||||
* - HEVAG: very off
|
||||
* - XMA2: exact file size
|
||||
* - some RIFX have LE size
|
||||
* (later we'll validate "data" which fortunately is correct)
|
||||
*/
|
||||
if (read_32bit(0x04,sf)+0x04+0x04 != ww.file_size) {
|
||||
VGM_LOG("WWISE: bad riff size (real=0x%x vs riff=0x%x)\n", read_32bit(0x04,sf)+0x04+0x04, ww.file_size);
|
||||
goto fail;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ignore LyN RIFF */
|
||||
{
|
||||
off_t fact_offset;
|
||||
size_t fact_size;
|
||||
|
||||
if (find_chunk(sf, 0x66616374,first_offset,0, &fact_offset,&fact_size, 0, 0)) { /* "fact" */
|
||||
if (fact_size == 0x10 && read_32bitBE(fact_offset+0x04, sf) == 0x4C794E20) /* "LyN " */
|
||||
goto fail; /* parsed elsewhere */
|
||||
}
|
||||
/* Wwise doesn't use "fact", though */
|
||||
}
|
||||
|
||||
/* parse format (roughly spec-compliant but some massaging is needed) */
|
||||
{
|
||||
off_t loop_offset;
|
||||
size_t loop_size;
|
||||
|
||||
/* find basic chunks */
|
||||
if (read_32bitBE(0x0c, sf) == 0x584D4132) { /* "XMA2" with no "fmt" [Too Human (X360)] */
|
||||
ww.format = 0x0165; /* signal for below */
|
||||
}
|
||||
else {
|
||||
if (!find_chunk(sf, 0x666d7420,first_offset,0, &ww.fmt_offset,&ww.fmt_size, ww.big_endian, 0)) /* "fmt " */
|
||||
goto fail;
|
||||
if (ww.fmt_size < 0x10)
|
||||
goto fail;
|
||||
ww.format = (uint16_t)read_16bit(ww.fmt_offset+0x00,sf);
|
||||
}
|
||||
|
||||
|
||||
if (ww.format == 0x0165) {
|
||||
/* pseudo-XMA2WAVEFORMAT ("fmt"+"XMA2" or just "XMA2) */
|
||||
if (!find_chunk(sf, 0x584D4132,first_offset,0, &ww.chunk_offset,NULL, ww.big_endian, 0)) /* "XMA2" */
|
||||
goto fail;
|
||||
xma2_parse_xma2_chunk(sf, ww.chunk_offset,&ww.channels,&ww.sample_rate, &ww.loop_flag, &ww.num_samples, &ww.loop_start_sample, &ww.loop_end_sample);
|
||||
}
|
||||
else {
|
||||
/* pseudo-WAVEFORMATEX */
|
||||
ww.channels = read_16bit(ww.fmt_offset+0x02,sf);
|
||||
ww.sample_rate = read_32bit(ww.fmt_offset+0x04,sf);
|
||||
ww.average_bps = read_32bit(ww.fmt_offset+0x08,sf);/* bytes per sec */
|
||||
ww.block_align = (uint16_t)read_16bit(ww.fmt_offset+0x0c,sf);
|
||||
ww.bits_per_sample = (uint16_t)read_16bit(ww.fmt_offset+0x0e,sf);
|
||||
if (ww.fmt_size > 0x10 && ww.format != 0x0165 && ww.format != 0x0166) /* ignore XMAWAVEFORMAT */
|
||||
ww.extra_size = (uint16_t)read_16bit(ww.fmt_offset+0x10,sf);
|
||||
if (ww.extra_size >= 0x06) { /* always present (actual RIFFs only have it in WAVEFORMATEXTENSIBLE) */
|
||||
/* mostly WAVEFORMATEXTENSIBLE's bitmask (see AkSpeakerConfig.h) */
|
||||
ww.channel_layout = read_32bit(ww.fmt_offset+0x14,sf);
|
||||
/* later games (+2018?) have a pseudo-format instead to handle more cases:
|
||||
* - 8b: uNumChannels
|
||||
* - 4b: eConfigType (0=none, 1=standard, 2=ambisonic)
|
||||
* - 19b: uChannelMask */
|
||||
if ((ww.channel_layout & 0xFF) == ww.channels) {
|
||||
ww.channel_layout = (ww.channel_layout >> 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* find loop info ("XMA2" chunks already read them) */
|
||||
if (ww.format == 0x0166) { /* XMA2WAVEFORMATEX in fmt */
|
||||
ww.chunk_offset = ww.fmt_offset;
|
||||
xma2_parse_fmt_chunk_extra(sf, ww.chunk_offset, &ww.loop_flag, &ww.num_samples, &ww.loop_start_sample, &ww.loop_end_sample, ww.big_endian);
|
||||
}
|
||||
else if (find_chunk(sf, 0x736D706C,first_offset,0, &loop_offset,&loop_size, ww.big_endian, 0)) { /* "smpl", common */
|
||||
if (loop_size >= 0x34
|
||||
&& read_32bit(loop_offset+0x1c, sf)==1 /* loop count */
|
||||
&& read_32bit(loop_offset+0x24+4, sf)==0) {
|
||||
ww.loop_flag = 1;
|
||||
ww.loop_start_sample = read_32bit(loop_offset+0x24+0x8, sf);
|
||||
ww.loop_end_sample = read_32bit(loop_offset+0x24+0xc, sf) + 1; /* like standard RIFF */
|
||||
}
|
||||
}
|
||||
//else if (find_chunk(sf, 0x4C495354,first_offset,0, &loop_offset,&loop_size, ww.big_endian, 0)) { /*"LIST", common */
|
||||
// /* usually contains "cue"s with sample positions for events (ex. Platinum Games) but no real looping info */
|
||||
//}
|
||||
|
||||
/* other chunks:
|
||||
* "JUNK": optional padding for aligment (0-size JUNK exists too), also in regular RIFF
|
||||
* "akd ": seem to store extra info for Wwise editor (wave peaks/loudness/HDR envelope?)
|
||||
*/
|
||||
|
||||
if (!find_chunk(sf, 0x64617461,first_offset,0, &ww.data_offset,&ww.data_size, ww.big_endian, 0)) /* "data" */
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* format to codec */
|
||||
switch(ww.format) {
|
||||
case 0x0001: ww.codec = PCM; break; /* older Wwise */
|
||||
case 0x0002: ww.codec = IMA; break; /* newer Wwise (conflicts with MSADPCM, probably means "platform's ADPCM") */
|
||||
case 0x0069: ww.codec = IMA; break; /* older Wwise [Spiderman Web of Shadows (X360), LotR Conquest (PC)] */
|
||||
case 0x0161: ww.codec = XWMA; break; /* WMAv2 */
|
||||
case 0x0162: ww.codec = XWMA; break; /* WMAPro */
|
||||
case 0x0165: ww.codec = XMA2; break; /* XMA2-chunk XMA (Wwise doesn't use XMA1) */
|
||||
case 0x0166: ww.codec = XMA2; break; /* fmt-chunk XMA */
|
||||
case 0xAAC0: ww.codec = AAC; break;
|
||||
case 0xFFF0: ww.codec = DSP; break;
|
||||
case 0xFFFB: ww.codec = HEVAG; break;
|
||||
case 0xFFFC: ww.codec = ATRAC9; break;
|
||||
case 0xFFFE: ww.codec = PCM; break; /* "PCM for Wwise Authoring" */
|
||||
case 0xFFFF: ww.codec = VORBIS; break;
|
||||
case 0x3039: ww.codec = OPUSNX; break; /* later renamed from "OPUS" */
|
||||
case 0x3040: ww.codec = OPUS; break;
|
||||
case 0x8311: ww.codec = PTADPCM; break; /* newer, rare [Genshin Impact (PC)] */
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* identify system's ADPCM */
|
||||
if (ww.format == 0x0002) {
|
||||
if (ww.extra_size == 0x0c + ww.channels * 0x2e) {
|
||||
/* newer Wwise DSP with coefs [Epic Mickey 2 (Wii), Batman Arkham Origins Blackgate (3DS)] */
|
||||
ww.codec = DSP;
|
||||
} else if (ww.extra_size == 0x0a && find_chunk(sf, 0x57696948, first_offset,0, NULL,NULL, ww.big_endian, 0)) { /* WiiH */
|
||||
/* few older Wwise DSP with num_samples in extra_size [Tony Hawk: Shred (Wii)] */
|
||||
ww.codec = DSP;
|
||||
} else if (ww.block_align == 0x104 * ww.channels) {
|
||||
ww.codec = PTADPCM; /* Bayonetta 2 (Switch) */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Some Wwise .bnk (RAM) files have truncated, prefetch mirrors of another file, that
|
||||
* play while the rest of the real stream loads. We'll add basic support to avoid
|
||||
* complaints of this or that .wem not playing */
|
||||
if (ww.data_offset + ww.data_size > ww.file_size) {
|
||||
//VGM_LOG("WWISE: truncated data size (prefetch): (real=0x%x > riff=0x%x)\n", ww.data_size, ww.file_size);
|
||||
|
||||
/* catch wrong rips as truncated tracks' file_size should be much smaller than data_size,
|
||||
* but it's possible to pre-fetch small files too [Punch Out!! (Wii)] */
|
||||
if (ww.data_offset + ww.data_size - ww.file_size < 0x5000 && ww.file_size > 0x10000) {
|
||||
VGM_LOG("WWISE: wrong expected data_size\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ww.codec == PCM || ww.codec == IMA || ww.codec == VORBIS || ww.codec == DSP || ww.codec == XMA2 ||
|
||||
ww.codec == OPUSNX || ww.codec == OPUS || ww.codec == PTADPCM) {
|
||||
ww.truncated = 1; /* only seen those, probably all exist (XWMA, AAC, HEVAG, ATRAC9?) */
|
||||
} else {
|
||||
VGM_LOG("WWISE: wrong size, maybe truncated\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
read_u32 = ww.big_endian ? read_u32be : read_u32le;
|
||||
read_s32 = ww.big_endian ? read_s32be : read_s32le;
|
||||
read_u16 = ww.big_endian ? read_u16be : read_u16le;
|
||||
|
||||
start_offset = ww.data_offset;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(ww.channels,ww.loop_flag);
|
||||
vgmstream = allocate_vgmstream(ww.channels, ww.loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_WWISE_RIFF;
|
||||
vgmstream->sample_rate = ww.sample_rate;
|
||||
vgmstream->loop_start_sample = ww.loop_start_sample;
|
||||
vgmstream->loop_end_sample = ww.loop_end_sample;
|
||||
vgmstream->channel_layout = ww.channel_layout;
|
||||
vgmstream->meta_type = meta_WWISE_RIFF;
|
||||
|
||||
switch(ww.codec) {
|
||||
case PCM: /* common */
|
||||
VGM_LOG("1\n");
|
||||
/* normally riff.c has priority but it's needed when .wem is used */
|
||||
if (ww.fmt_size != 0x10 && ww.fmt_size != 0x18 && ww.fmt_size != 0x28) goto fail; /* old, new/Limbo (PC) */
|
||||
if (ww.bits_per_sample != 16) goto fail;
|
||||
@ -296,10 +138,10 @@ VGM_LOG("1\n");
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
case VORBIS: { /* common */
|
||||
case VORBIS: { /* common */
|
||||
/* Wwise uses custom Vorbis, which changed over time (config must be detected to pass to the decoder). */
|
||||
off_t vorb_offset, data_offsets, block_offsets;
|
||||
size_t vorb_size, setup_offset, audio_offset;
|
||||
off_t data_offsets, block_offsets;
|
||||
size_t setup_offset, audio_offset;
|
||||
vorbis_custom_config cfg = {0};
|
||||
|
||||
cfg.channels = ww.channels;
|
||||
@ -309,10 +151,10 @@ VGM_LOG("1\n");
|
||||
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail; /* always 0 for Worbis */
|
||||
|
||||
/* autodetect format (fields are mostly common, see the end of the file) */
|
||||
if (find_chunk(sf, 0x766F7262,first_offset,0, &vorb_offset,&vorb_size, ww.big_endian, 0)) { /* "vorb" */
|
||||
if (ww.vorb_offset) {
|
||||
/* older Wwise (~<2012) */
|
||||
|
||||
switch(vorb_size) {
|
||||
switch(ww.vorb_size) {
|
||||
case 0x2C: /* earliest (~2009) [The Lord of the Rings: Conquest (PC)] */
|
||||
case 0x28: /* early (~2009) [UFC Undisputed 2009 (PS3), some EVE Online Apocrypha (PC)] */
|
||||
data_offsets = 0x18;
|
||||
@ -340,22 +182,22 @@ VGM_LOG("1\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
VGM_LOG("WWISE: unknown vorb size 0x%x\n", vorb_size);
|
||||
VGM_LOG("WWISE: unknown vorb size 0x%x\n", ww.vorb_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_samples = read_32bit(vorb_offset + 0x00, sf);
|
||||
setup_offset = read_32bit(vorb_offset + data_offsets + 0x00, sf); /* within data (0 = no seek table) */
|
||||
audio_offset = read_32bit(vorb_offset + data_offsets + 0x04, sf); /* within data */
|
||||
vgmstream->num_samples = read_s32(ww.vorb_offset + 0x00, sf);
|
||||
setup_offset = read_u32(ww.vorb_offset + data_offsets + 0x00, sf); /* within data (0 = no seek table) */
|
||||
audio_offset = read_u32(ww.vorb_offset + data_offsets + 0x04, sf); /* within data */
|
||||
if (block_offsets) {
|
||||
cfg.blocksize_1_exp = read_8bit(vorb_offset + block_offsets + 0x00, sf); /* small */
|
||||
cfg.blocksize_0_exp = read_8bit(vorb_offset + block_offsets + 0x01, sf); /* big */
|
||||
cfg.blocksize_1_exp = read_u8(ww.vorb_offset + block_offsets + 0x00, sf); /* small */
|
||||
cfg.blocksize_0_exp = read_u8(ww.vorb_offset + block_offsets + 0x01, sf); /* big */
|
||||
}
|
||||
ww.data_size -= audio_offset;
|
||||
|
||||
|
||||
/* detect normal packets */
|
||||
if (vorb_size == 0x2a) {
|
||||
if (ww.vorb_size == 0x2a) {
|
||||
/* almost all blocksizes are 0x08+0x0B except a few with 0x0a+0x0a [Captain America: Super Soldier (X360) voices/sfx] */
|
||||
if (cfg.blocksize_0_exp == cfg.blocksize_1_exp)
|
||||
cfg.packet_type = WWV_STANDARD;
|
||||
@ -365,12 +207,12 @@ VGM_LOG("1\n");
|
||||
* - full inline: ~2009, ex. The King of Fighters XII (X360), The Saboteur (PC)
|
||||
* - trimmed inline: ~2010, ex. Army of Two: 40 days (X360) some multiplayer files
|
||||
* - external: ~2010, ex. Assassin's Creed Brotherhood (X360), Dead Nation (X360) */
|
||||
if (vorb_size == 0x34) {
|
||||
size_t setup_size = (uint16_t)read_16bit(start_offset + setup_offset, sf);
|
||||
uint32_t id = (uint32_t)read_32bitBE(start_offset + setup_offset + 0x06, sf);
|
||||
if (ww.vorb_size == 0x34) {
|
||||
size_t setup_size = read_u16 (start_offset + setup_offset + 0x00, sf);
|
||||
uint32_t setup_id = read_u32be(start_offset + setup_offset + 0x06, sf);
|
||||
|
||||
/* if the setup after header starts with "(data)BCV" it's an inline codebook) */
|
||||
if ((id & 0x00FFFFFF) == 0x00424356) { /* 0"BCV" */
|
||||
if ((setup_id & 0x00FFFFFF) == 0x00424356) { /* 0"BCV" */
|
||||
cfg.setup_type = WWV_FULL_SETUP;
|
||||
}
|
||||
/* if the setup is suspiciously big it's probably trimmed inline codebooks */
|
||||
@ -401,15 +243,15 @@ VGM_LOG("1\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
VGM_LOG("WWISE: unknown extra size 0x%x\n", vorb_size);
|
||||
VGM_LOG("WWISE: unknown extra size 0x%x\n", ww.vorb_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_samples = read_32bit(extra_offset + 0x00, sf);
|
||||
setup_offset = read_32bit(extra_offset + data_offsets + 0x00, sf); /* within data */
|
||||
audio_offset = read_32bit(extra_offset + data_offsets + 0x04, sf); /* within data */
|
||||
cfg.blocksize_1_exp = read_8bit(extra_offset + block_offsets + 0x00, sf); /* small */
|
||||
cfg.blocksize_0_exp = read_8bit(extra_offset + block_offsets + 0x01, sf); /* big */
|
||||
vgmstream->num_samples = read_s32(extra_offset + 0x00, sf);
|
||||
setup_offset = read_u32(extra_offset + data_offsets + 0x00, sf); /* within data */
|
||||
audio_offset = read_u32(extra_offset + data_offsets + 0x04, sf); /* within data */
|
||||
cfg.blocksize_1_exp = read_u8(extra_offset + block_offsets + 0x00, sf); /* small */
|
||||
cfg.blocksize_0_exp = read_u8(extra_offset + block_offsets + 0x01, sf); /* big */
|
||||
ww.data_size -= audio_offset;
|
||||
|
||||
/* detect normal packets */
|
||||
@ -444,10 +286,7 @@ VGM_LOG("1\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
case DSP: { /* Wii/3DS/WiiU */
|
||||
off_t wiih_offset;
|
||||
size_t wiih_size;
|
||||
|
||||
case DSP: { /* Wii/3DS/WiiU */
|
||||
//if (ww.fmt_size != 0x28 && ww.fmt_size != ?) goto fail; /* old, new */
|
||||
if (ww.bits_per_sample != 4) goto fail;
|
||||
|
||||
@ -456,17 +295,17 @@ VGM_LOG("1\n");
|
||||
vgmstream->interleave_block_size = 0x08; /* ww.block_align = 0x8 in older Wwise, samples per block in newer Wwise */
|
||||
|
||||
/* find coef position */
|
||||
if (find_chunk(sf, 0x57696948,first_offset,0, &wiih_offset,&wiih_size, ww.big_endian, 0)) { /*"WiiH", older Wwise */
|
||||
if (ww.wiih_offset) { /* older */
|
||||
vgmstream->num_samples = dsp_bytes_to_samples(ww.data_size, ww.channels);
|
||||
if (wiih_size != 0x2e * ww.channels) goto fail;
|
||||
if (ww.wiih_size != 0x2e * ww.channels) goto fail;
|
||||
|
||||
if (is_dsp_full_interleave(sf, &ww, wiih_offset))
|
||||
if (is_dsp_full_interleave(sf, &ww, ww.wiih_offset))
|
||||
vgmstream->interleave_block_size = ww.data_size / 2;
|
||||
}
|
||||
else if (ww.extra_size == 0x0c + ww.channels * 0x2e) { /* newer Wwise */
|
||||
vgmstream->num_samples = read_32bit(ww.fmt_offset + 0x18, sf);
|
||||
wiih_offset = ww.fmt_offset + 0x1c;
|
||||
wiih_size = 0x2e * ww.channels;
|
||||
else if (ww.extra_size == 0x0c + ww.channels * 0x2e) { /* newer */
|
||||
vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf);
|
||||
ww.wiih_offset = ww.fmt_offset + 0x1c;
|
||||
ww.wiih_size = 0x2e * ww.channels;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
@ -484,18 +323,16 @@ VGM_LOG("1\n");
|
||||
vgmstream->loop_flag = 0;
|
||||
}
|
||||
|
||||
dsp_read_coefs(vgmstream,sf,wiih_offset + 0x00, 0x2e, ww.big_endian);
|
||||
dsp_read_hist (vgmstream,sf,wiih_offset + 0x24, 0x2e, ww.big_endian);
|
||||
dsp_read_coefs(vgmstream, sf, ww.wiih_offset + 0x00, 0x2e, ww.big_endian);
|
||||
dsp_read_hist (vgmstream, sf, ww.wiih_offset + 0x24, 0x2e, ww.big_endian);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case XMA2: { /* X360/XBone */
|
||||
case XMA2: { /* X360/XBone */
|
||||
uint8_t buf[0x100];
|
||||
int bytes;
|
||||
off_t xma2_offset;
|
||||
size_t xma2_size;
|
||||
|
||||
/* endian check should be enough */
|
||||
//if (ww.fmt_size != ...) goto fail; /* XMA1 0x20, XMA2old: 0x34, XMA2new: 0x40, XMA2 Guitar Hero Live/padded: 0x64, etc */
|
||||
@ -504,10 +341,11 @@ VGM_LOG("1\n");
|
||||
if (!(ww.big_endian || (!ww.big_endian && check_extensions(sf,"wem,bnk"))))
|
||||
goto fail;
|
||||
|
||||
if (find_chunk(sf, 0x584D4132,first_offset,0, &xma2_offset,&xma2_size, ww.big_endian, 0)) { /*"XMA2"*/ /* older Wwise */
|
||||
bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf,0x100, xma2_offset, xma2_size, ww.data_size, sf);
|
||||
} else { /* newer Wwise */
|
||||
bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,0x100, ww.fmt_offset, ww.fmt_size, ww.data_size, sf, ww.big_endian);
|
||||
if (ww.xma2_offset) { /* older */
|
||||
bytes = ffmpeg_make_riff_xma2_from_xma2_chunk(buf, sizeof(buf), ww.xma2_offset, ww.xma2_size, ww.data_size, sf);
|
||||
}
|
||||
else { /* newer */
|
||||
bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, sizeof(buf), ww.fmt_offset, ww.fmt_size, ww.data_size, sf, ww.big_endian);
|
||||
}
|
||||
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, ww.data_offset,ww.data_size);
|
||||
@ -518,23 +356,19 @@ VGM_LOG("1\n");
|
||||
vgmstream->num_samples = ww.num_samples; /* set while parsing XMAWAVEFORMATs */
|
||||
|
||||
/* Wwise loops are always pre-adjusted (old or new) and only num_samples is off */
|
||||
xma_fix_raw_samples(vgmstream, sf, ww.data_offset,ww.data_size, ww.chunk_offset, 1,0);
|
||||
|
||||
/* "XMAc": rare Wwise extension, XMA2 physical loop regions (loop_start_b, loop_end_b, loop_subframe_data)
|
||||
* Can appear even in the file doesn't loop, maybe it's meant to be the playable physical region */
|
||||
//VGM_ASSERT(find_chunk(sf, 0x584D4163,first_offset,0, NULL,NULL, ww.big_endian, 0), "WWISE: XMAc chunk found\n");
|
||||
/* other chunks: "seek", regular XMA2 seek table */
|
||||
xma_fix_raw_samples(vgmstream, sf, ww.data_offset, ww.data_size, ww.xma2_offset ? ww.xma2_offset : ww.fmt_offset, 1,0);
|
||||
|
||||
/* XMA is VBR so this is very approximate percent, meh */
|
||||
if (ww.truncated) {
|
||||
vgmstream->num_samples = (int32_t)(vgmstream->num_samples *
|
||||
(double)(ww.file_size - start_offset) / (double)ww.data_size);
|
||||
//todo data size, call function
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case XWMA: { /* X360 */
|
||||
case XWMA: { /* X360 */
|
||||
ffmpeg_codec_data *ffmpeg_data = NULL;
|
||||
uint8_t buf[0x100];
|
||||
int bytes;
|
||||
@ -542,8 +376,8 @@ VGM_LOG("1\n");
|
||||
if (ww.fmt_size != 0x18) goto fail;
|
||||
if (!ww.big_endian) goto fail; /* must be from Wwise X360 (PC LE XWMA is parsed elsewhere) */
|
||||
|
||||
bytes = ffmpeg_make_riff_xwma(buf,0x100, ww.format, ww.data_size, vgmstream->channels, vgmstream->sample_rate, ww.average_bps, ww.block_align);
|
||||
ffmpeg_data = init_ffmpeg_header_offset(sf, buf,bytes, ww.data_offset,ww.data_size);
|
||||
bytes = ffmpeg_make_riff_xwma(buf, sizeof(buf), ww.format, ww.data_size, ww.channels, ww.sample_rate, ww.average_bps, ww.block_align);
|
||||
ffmpeg_data = init_ffmpeg_header_offset(sf, buf,bytes, ww.data_offset, ww.data_size);
|
||||
if ( !ffmpeg_data ) goto fail;
|
||||
vgmstream->codec_data = ffmpeg_data;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
@ -559,9 +393,9 @@ VGM_LOG("1\n");
|
||||
msd.data_size = ww.data_size;
|
||||
|
||||
if (ww.format == 0x0162)
|
||||
wmapro_get_samples(&msd, sf, ww.block_align, ww.sample_rate,0x00E0);
|
||||
wmapro_get_samples(&msd, sf, ww.block_align, ww.sample_rate, 0x00E0);
|
||||
else
|
||||
wma_get_samples(&msd, sf, ww.block_align, ww.sample_rate,0x001F);
|
||||
wma_get_samples(&msd, sf, ww.block_align, ww.sample_rate, 0x001F);
|
||||
|
||||
vgmstream->num_samples = msd.num_samples;
|
||||
if (!vgmstream->num_samples)
|
||||
@ -592,32 +426,28 @@ VGM_LOG("1\n");
|
||||
|
||||
case OPUSNX: { /* Switch */
|
||||
size_t skip;
|
||||
size_t seek_size;
|
||||
|
||||
if (ww.fmt_size != 0x28) goto fail;
|
||||
/* values up to 0x14 seem fixed and similar to HEVAG's (block_align 0x02/04, bits_per_sample 0x10) */
|
||||
if (ww.fmt_size == 0x28) {
|
||||
size_t seek_size;
|
||||
|
||||
vgmstream->num_samples = read_32bit(ww.fmt_offset + 0x18, sf);
|
||||
/* 0x1c: null? 0x20: data_size without seek_size */
|
||||
seek_size = read_32bit(ww.fmt_offset + 0x24, sf);
|
||||
vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf);
|
||||
/* 0x1c: null?
|
||||
* 0x20: data_size without seek_size */
|
||||
seek_size = read_u32(ww.fmt_offset + 0x24, sf);
|
||||
|
||||
start_offset += seek_size;
|
||||
ww.data_size -= seek_size;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
start_offset += seek_size;
|
||||
ww.data_size -= seek_size;
|
||||
|
||||
skip = switch_opus_get_encoder_delay(start_offset, sf); /* should be 120 */
|
||||
|
||||
/* some voices have original sample rate but opus can only do 48000 (ex. Mario Kart Home Circuit 24khz) */
|
||||
/* some voices have original sample rate but OPUS can only do 48000 (ex. Mario Kart Home Circuit 24khz) */
|
||||
if (vgmstream->sample_rate != 48000) {
|
||||
vgmstream->sample_rate = 48000;
|
||||
vgmstream->num_samples = switch_opus_get_samples(start_offset,ww.data_size, sf); /* also original's */
|
||||
vgmstream->num_samples -= skip;
|
||||
}
|
||||
|
||||
|
||||
/* OPUS is VBR so this is very approximate percent, meh */
|
||||
if (ww.truncated) {
|
||||
vgmstream->num_samples = (int32_t)(vgmstream->num_samples *
|
||||
@ -625,18 +455,18 @@ VGM_LOG("1\n");
|
||||
ww.data_size = ww.file_size - start_offset;
|
||||
}
|
||||
|
||||
vgmstream->codec_data = init_ffmpeg_switch_opus(sf, start_offset,ww.data_size, vgmstream->channels, skip, vgmstream->sample_rate);
|
||||
vgmstream->codec_data = init_ffmpeg_switch_opus(sf, start_offset,ww.data_size, ww.channels, skip, vgmstream->sample_rate);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
}
|
||||
|
||||
case OPUS: { /* PC/mobile/etc, rare (most games still use Vorbis) [Girl Cafe Gun (Mobile)] */
|
||||
case OPUS: { /* alt to Vorbis [Girl Cafe Gun (Mobile)] */
|
||||
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
|
||||
|
||||
/* extra: size 0x12 */
|
||||
vgmstream->num_samples = read_32bit(ww.fmt_offset + 0x18, sf);
|
||||
vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf);
|
||||
/* 0x1c: stream size without OggS? */
|
||||
/* 0x20: full samples (without encoder delay) */
|
||||
|
||||
@ -647,15 +477,45 @@ VGM_LOG("1\n");
|
||||
ww.data_size = ww.file_size - start_offset;
|
||||
}
|
||||
|
||||
vgmstream->codec_data = init_ffmpeg_offset(sf, ww.data_offset,ww.data_size);
|
||||
vgmstream->codec_data = init_ffmpeg_offset(sf, ww.data_offset, ww.data_size);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
}
|
||||
|
||||
#if 0 // disabled until more files/tests
|
||||
case OPUSWW: { /* updated Opus [Assassin's Creed Valhalla (PC)] */
|
||||
int skip, table_count;
|
||||
|
||||
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
|
||||
if (!ww.seek_offset)) goto fail;
|
||||
|
||||
/* extra: size 0x10 */
|
||||
/* 0x12: samples per frame */
|
||||
vgmstream->num_samples = read_s32(ww.fmt_offset + 0x18, sf);
|
||||
table_count = read_u32(ww.fmt_offset + 0x1c, sf); /* same as seek size / 2 */
|
||||
skip = read_u16(ww.fmt_offset + 0x20, sf);
|
||||
/* 0x22: 1? (though extra size is declared as 0x10 so this is outsize, AK plz */
|
||||
|
||||
/* OPUS is VBR so this is very approximate percent, meh */
|
||||
if (ww.truncated) {
|
||||
vgmstream->num_samples = (int32_t)(vgmstream->num_samples *
|
||||
(double)(ww.file_size - start_offset) / (double)ww.data_size);
|
||||
ww.data_size = ww.file_size - start_offset;
|
||||
}
|
||||
|
||||
/* Wwise Opus saves all frame sizes in the seek table */
|
||||
vgmstream->codec_data = init_ffmpeg_wwise_opus(sf, ww.seek_offset, table_count, ww.data_offset, ww.data_size, ww.channels, skip);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case HEVAG: /* PSV */
|
||||
|
||||
#endif
|
||||
case HEVAG: /* PSV */
|
||||
/* changed values, another bizarre Wwise quirk */
|
||||
//ww.block_align /* unknown (1ch=2, 2ch=4) */
|
||||
//ww.bits_per_sample; /* unknown (0x10) */
|
||||
@ -664,7 +524,9 @@ VGM_LOG("1\n");
|
||||
if (ww.fmt_size != 0x18) goto fail;
|
||||
if (ww.big_endian) goto fail;
|
||||
|
||||
/* extra_data: size 0x06, @0x00: samples per block (0x1c), @0x04: channel config */
|
||||
/* extra_data (size 0x06)
|
||||
* 0x00: samples per block (0x1c)
|
||||
* 0x04: channel config (again?) */
|
||||
|
||||
vgmstream->coding_type = coding_HEVAG;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
@ -680,20 +542,20 @@ VGM_LOG("1\n");
|
||||
if (ww.fmt_size != 0x24) goto fail;
|
||||
if (ww.extra_size != 0x12) goto fail;
|
||||
|
||||
cfg.channels = vgmstream->channels;
|
||||
cfg.config_data = read_32bitBE(ww.fmt_offset+0x18,sf);
|
||||
cfg.encoder_delay = read_32bit(ww.fmt_offset+0x20,sf);
|
||||
cfg.channels = ww.channels;
|
||||
cfg.config_data = read_u32be(ww.fmt_offset + 0x18,sf);
|
||||
cfg.encoder_delay = read_u32(ww.fmt_offset + 0x20,sf);
|
||||
|
||||
vgmstream->codec_data = init_atrac9(&cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_ATRAC9;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
vgmstream->num_samples = read_32bit(ww.fmt_offset+0x1c,sf);
|
||||
vgmstream->num_samples = read_s32(ww.fmt_offset + 0x1c, sf);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case PTADPCM: /* substitutes IMA as default ADPCM codec */
|
||||
case PTADPCM: /* newer ADPCM [Bayonetta 2 (Switch), Genshin Impact (PC)] */
|
||||
if (ww.bits_per_sample != 4) goto fail;
|
||||
if (ww.block_align != 0x24 * ww.channels && ww.block_align != 0x104 * ww.channels) goto fail;
|
||||
|
||||
@ -714,8 +576,7 @@ VGM_LOG("1\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ( !vgmstream_open_stream(vgmstream,sf,start_offset) )
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset) )
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
@ -760,6 +621,219 @@ static int is_dsp_full_interleave(STREAMFILE* sf, wwise_header* ww, off_t coef_o
|
||||
}
|
||||
|
||||
|
||||
static int parse_wwise(STREAMFILE* sf, wwise_header* ww) {
|
||||
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
|
||||
uint16_t (*read_u16)(off_t,STREAMFILE*) = NULL;
|
||||
|
||||
if (read_u32be(0x00,sf) != 0x52494646 && /* "RIFF" (LE) */
|
||||
read_u32be(0x00,sf) != 0x52494658) /* "RIFX" (BE) */
|
||||
goto fail;
|
||||
if (read_u32be(0x08,sf) != 0x57415645 && /* "WAVE" */
|
||||
read_u32be(0x08,sf) != 0x58574D41) /* "XWMA" */
|
||||
goto fail;
|
||||
|
||||
ww->big_endian = read_u32be(0x00,sf) == 0x52494658; /* RIFX */
|
||||
if (ww->big_endian) { /* Wwise honors machine's endianness (PC=RIFF, X360=RIFX --unlike XMA) */
|
||||
read_u32 = read_u32be;
|
||||
read_u16 = read_u16be;
|
||||
} else {
|
||||
read_u32 = read_u32le;
|
||||
read_u16 = read_u16le;
|
||||
}
|
||||
|
||||
ww->file_size = get_streamfile_size(sf);
|
||||
|
||||
#if 0
|
||||
/* Wwise's RIFF size is often wonky, seemingly depending on codec:
|
||||
* - PCM, IMA/PTADPCM, VORBIS, AAC, OPUSNX/OPUS: correct
|
||||
* - DSP, XWMA, ATRAC9: almost always slightly smaller (around 0x50)
|
||||
* - HEVAG: very off
|
||||
* - XMA2: exact file size
|
||||
* - some RIFX have LE size
|
||||
* (later we'll validate "data" which fortunately is correct)
|
||||
*/
|
||||
if (read_u32(0x04,sf) + 0x04 + 0x04 != ww->file_size) {
|
||||
VGM_LOG("WWISE: bad riff size\n");
|
||||
goto fail;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* parse chunks (reads once linearly) */
|
||||
{
|
||||
off_t offset = 0x0c;
|
||||
while (offset < ww->file_size) {
|
||||
uint32_t type = read_u32be(offset + 0x00,sf);
|
||||
uint32_t size = read_u32 (offset + 0x04,sf);
|
||||
offset += 0x08;
|
||||
|
||||
switch(type) {
|
||||
case 0x666d7420: /* "fmt " */
|
||||
ww->fmt_offset = offset;
|
||||
ww->fmt_size = size;
|
||||
break;
|
||||
case 0x584D4132: /* "XMA2" */
|
||||
ww->xma2_offset = offset;
|
||||
ww->xma2_size = size;
|
||||
break;
|
||||
case 0x64617461: /* "data" */
|
||||
ww->data_offset = offset;
|
||||
ww->data_size = size;
|
||||
break;
|
||||
case 0x766F7262: /* "vorb" */
|
||||
ww->vorb_offset = offset;
|
||||
ww->vorb_size = size;
|
||||
break;
|
||||
case 0x57696948: /* "WiiH" */
|
||||
ww->wiih_offset = offset;
|
||||
ww->wiih_size = size;
|
||||
break;
|
||||
case 0x7365656B: /* "seek" */
|
||||
ww->seek_offset = offset;
|
||||
ww->seek_size = size;
|
||||
break;
|
||||
case 0x736D706C: /* "smpl" */
|
||||
ww->smpl_offset = offset;
|
||||
ww->smpl_size = size;
|
||||
break;
|
||||
|
||||
case 0x66616374: /* "fact" */
|
||||
/* Wwise shouldn't use fact, but if somehow some file does uncomment the following: */
|
||||
//if (size == 0x10 && read_u32be(offset + 0x04, sf) == 0x4C794E20) /* "LyN " */
|
||||
// goto fail; /* ignore LyN RIFF */
|
||||
goto fail;
|
||||
|
||||
/* "XMAc": rare XMA2 physical loop regions (loop_start_b, loop_end_b, loop_subframe_data)
|
||||
* Can appear even in the file doesn't loop, maybe it's meant to be the playable physical region */
|
||||
/* "LIST": leftover 'cue' info from OG .wavs (ex. loop starts in Platinum Games) */
|
||||
/* "JUNK": optional padding for aligment (0-size JUNK exists too) */
|
||||
/* "akd ": extra info for Wwise? (wave peaks/loudness/HDR envelope?) */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* chunks are even-aligned and don't need to add padding byte, unlike real RIFFs */
|
||||
offset += size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* parse format (roughly spec-compliant but some massaging is needed) */
|
||||
if (ww->xma2_offset) {
|
||||
/* pseudo-XMA2WAVEFORMAT, "fmt"+"XMA2" (common) or only "XMA2" [Too Human (X360)] */
|
||||
ww->format = 0x0165; /* signal for below */
|
||||
xma2_parse_xma2_chunk(sf,
|
||||
ww->xma2_offset, &ww->channels, &ww->sample_rate, &ww->loop_flag,
|
||||
&ww->num_samples, &ww->loop_start_sample, &ww->loop_end_sample);
|
||||
}
|
||||
else {
|
||||
/* pseudo-WAVEFORMATEX */
|
||||
if (ww->fmt_size < 0x10)
|
||||
goto fail;
|
||||
ww->format = read_u16(ww->fmt_offset + 0x00,sf);
|
||||
ww->channels = read_u16(ww->fmt_offset + 0x02,sf);
|
||||
ww->sample_rate = read_u32(ww->fmt_offset + 0x04,sf);
|
||||
ww->average_bps = read_u32(ww->fmt_offset + 0x08,sf);
|
||||
ww->block_align = read_u16(ww->fmt_offset + 0x0c,sf);
|
||||
ww->bits_per_sample = read_u16(ww->fmt_offset + 0x0e,sf);
|
||||
if (ww->fmt_size > 0x10 && ww->format != 0x0165 && ww->format != 0x0166) /* ignore XMAWAVEFORMAT */
|
||||
ww->extra_size = read_u16(ww->fmt_offset + 0x10,sf);
|
||||
if (ww->extra_size >= 0x06) { /* always present (actual RIFFs only have it in WAVEFORMATEXTENSIBLE) */
|
||||
/* mostly WAVEFORMATEXTENSIBLE's bitmask (see AkSpeakerConfig.h) */
|
||||
ww->channel_layout = read_u32(ww->fmt_offset + 0x14,sf);
|
||||
/* later games (+2018?) have a pseudo-format instead to handle more cases:
|
||||
* - 8b: uNumChannels
|
||||
* - 4b: eConfigType (0=none, 1=standard, 2=ambisonic)
|
||||
* - 19b: uChannelMask */
|
||||
if ((ww->channel_layout & 0xFF) == ww->channels) {
|
||||
ww->channel_layout = (ww->channel_layout >> 12);
|
||||
}
|
||||
}
|
||||
|
||||
if (ww->format == 0x0166) { /* XMA2WAVEFORMATEX in fmt */
|
||||
xma2_parse_fmt_chunk_extra(sf, ww->fmt_offset, &ww->loop_flag,
|
||||
&ww->num_samples, &ww->loop_start_sample, &ww->loop_end_sample, ww->big_endian);
|
||||
}
|
||||
}
|
||||
|
||||
/* common loops ("XMA2" chunks already read them) */
|
||||
if (ww->smpl_offset) {
|
||||
if (ww->smpl_size >= 0x34
|
||||
&& read_u32(ww->smpl_offset + 0x1c, sf) == 1 /* loop count */
|
||||
&& read_u32(ww->smpl_offset + 0x24 + 0x04, sf) == 0) { /* loop type */
|
||||
ww->loop_flag = 1;
|
||||
ww->loop_start_sample = read_u32(ww->smpl_offset + 0x24 + 0x8, sf);
|
||||
ww->loop_end_sample = read_u32(ww->smpl_offset + 0x24 + 0xc, sf) + 1; /* +1 like standard RIFF */
|
||||
}
|
||||
}
|
||||
|
||||
if (!ww->data_offset)
|
||||
goto fail;
|
||||
|
||||
|
||||
/* format to codec */
|
||||
switch(ww->format) {
|
||||
case 0x0001: ww->codec = PCM; break; /* older Wwise */
|
||||
case 0x0002: ww->codec = IMA; break; /* newer Wwise (conflicts with MSADPCM, probably means "platform's ADPCM") */
|
||||
case 0x0069: ww->codec = IMA; break; /* older Wwise [Spiderman Web of Shadows (X360), LotR Conquest (PC)] */
|
||||
case 0x0161: ww->codec = XWMA; break; /* WMAv2 */
|
||||
case 0x0162: ww->codec = XWMA; break; /* WMAPro */
|
||||
case 0x0165: ww->codec = XMA2; break; /* XMA2-chunk XMA (Wwise doesn't use XMA1) */
|
||||
case 0x0166: ww->codec = XMA2; break; /* fmt-chunk XMA */
|
||||
case 0xAAC0: ww->codec = AAC; break;
|
||||
case 0xFFF0: ww->codec = DSP; break;
|
||||
case 0xFFFB: ww->codec = HEVAG; break;
|
||||
case 0xFFFC: ww->codec = ATRAC9; break;
|
||||
case 0xFFFE: ww->codec = PCM; break; /* "PCM for Wwise Authoring" */
|
||||
case 0xFFFF: ww->codec = VORBIS; break;
|
||||
case 0x3039: ww->codec = OPUSNX; break; /* renamed from "OPUS" on Wwise 2018.1 */
|
||||
case 0x3040: ww->codec = OPUS; break;
|
||||
case 0x3041: ww->codec = OPUSWW; break; /* added on Wwise 2019.2.3, presumably replaces OPUS */
|
||||
case 0x8311: ww->codec = PTADPCM; break; /* added on Wwise 2019.1, replaces IMA */
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* identify system's ADPCM */
|
||||
if (ww->format == 0x0002) {
|
||||
if (ww->extra_size == 0x0c + ww->channels * 0x2e) {
|
||||
/* newer Wwise DSP with coefs [Epic Mickey 2 (Wii), Batman Arkham Origins Blackgate (3DS)] */
|
||||
ww->codec = DSP;
|
||||
} else if (ww->extra_size == 0x0a && ww->wiih_offset) { /* WiiH */
|
||||
/* few older Wwise DSP with num_samples in extra_size [Tony Hawk: Shred (Wii)] */
|
||||
ww->codec = DSP;
|
||||
} else if (ww->block_align == 0x104 * ww->channels) {
|
||||
ww->codec = PTADPCM; /* Bayonetta 2 (Switch) */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Some Wwise .bnk (RAM) files have truncated, prefetch mirrors of another file, that
|
||||
* play while the rest of the real stream loads. We'll add basic support to avoid
|
||||
* complaints of this or that .wem not playing */
|
||||
if (ww->data_offset + ww->data_size > ww->file_size) {
|
||||
//;VGM_LOG("WWISE: truncated data size (prefetch): (real=0x%x > riff=0x%x)\n", ww->data_size, ww->file_size);
|
||||
|
||||
/* catch wrong rips as truncated tracks' file_size should be much smaller than data_size,
|
||||
* but it's possible to pre-fetch small files too [Punch Out!! (Wii)] */
|
||||
if (ww->data_offset + ww->data_size - ww->file_size < 0x5000 && ww->file_size > 0x10000) {
|
||||
VGM_LOG("WWISE: wrong expected data_size\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ww->codec == PCM || ww->codec == IMA || ww->codec == VORBIS || ww->codec == DSP || ww->codec == XMA2 ||
|
||||
ww->codec == OPUSNX || ww->codec == OPUS || ww->codec == OPUSWW || ww->codec == PTADPCM) {
|
||||
ww->truncated = 1; /* only seen those, probably all exist (XWMA, AAC, HEVAG, ATRAC9?) */
|
||||
} else {
|
||||
VGM_LOG("WWISE: wrong size, maybe truncated\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* VORBIS FORMAT RESEARCH */
|
||||
/*
|
||||
|
127
src/meta/xopus.c
127
src/meta/xopus.c
@ -1,63 +1,64 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* XOPUS - from Exient games [Angry Birds: Transformers (Android), Angry Birds: Go (Android)] */
|
||||
VGMSTREAM * init_vgmstream_xopus(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag = 0, channel_count, sample_rate, num_samples, skip;
|
||||
size_t data_size;
|
||||
int entries;
|
||||
|
||||
|
||||
/* checks*/
|
||||
if (!check_extensions(streamFile, "xopus"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, streamFile) != 0x584F7075) /* "XOpu" */
|
||||
goto fail;
|
||||
|
||||
/* 0x04: always 0x01? */
|
||||
channel_count = read_8bit(0x05, streamFile);
|
||||
/* 0x06: always 0x30? */
|
||||
/* 0x08: always 0xc8? max allowed packet size? */
|
||||
num_samples = read_32bitLE(0x0c, streamFile);
|
||||
skip = read_32bitLE(0x10, streamFile);
|
||||
entries = read_32bitLE(0x14, streamFile);
|
||||
data_size = read_32bitLE(0x18, streamFile);
|
||||
/* 0x1c: unused */
|
||||
/* 0x20+: packet sizes table */
|
||||
|
||||
sample_rate = 48000;
|
||||
loop_flag = 0;
|
||||
|
||||
start_offset = 0x20 + 0x02*entries;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_XOPUS;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
vgmstream->codec_data = init_ffmpeg_x_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;
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
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"
|
||||
|
||||
|
||||
/* XOPUS - from Exient games [Angry Birds: Transformers (Android), Angry Birds: Go (Android)] */
|
||||
VGMSTREAM* init_vgmstream_xopus(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset, table_offset;
|
||||
int loop_flag = 0, channels, sample_rate, num_samples, skip;
|
||||
size_t data_size;
|
||||
int entries;
|
||||
|
||||
|
||||
/* checks*/
|
||||
if (!check_extensions(sf, "xopus"))
|
||||
goto fail;
|
||||
if (read_u32be(0x00, sf) != 0x584F7075) /* "XOpu" */
|
||||
goto fail;
|
||||
|
||||
/* 0x04: always 0x01? */
|
||||
channels = read_u8(0x05, sf);
|
||||
/* 0x06: always 0x30? */
|
||||
/* 0x08: always 0xc8? max allowed packet size? */
|
||||
num_samples = read_s32le(0x0c, sf);
|
||||
skip = read_s32le(0x10, sf);
|
||||
entries = read_u32le(0x14, sf);
|
||||
data_size = read_u32le(0x18, sf);
|
||||
/* 0x1c: unused */
|
||||
/* 0x20+: packet sizes table */
|
||||
|
||||
sample_rate = 48000;
|
||||
loop_flag = 0;
|
||||
|
||||
table_offset = 0x20;
|
||||
start_offset = table_offset + 0x02*entries;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_XOPUS;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
vgmstream->codec_data = init_ffmpeg_x_opus(sf, table_offset, entries, start_offset, data_size, vgmstream->channels, skip);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
32
src/render.c
32
src/render.c
@ -404,13 +404,21 @@ static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
||||
}
|
||||
}
|
||||
|
||||
static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_to_do) {
|
||||
static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
int channels = vgmstream->pstate.output_channels;
|
||||
int start = 0;
|
||||
|
||||
/* since anything beyond pad end is silence no need to check ranges */
|
||||
/* since anything beyond pad end is silence no need to check end */
|
||||
if (ps->play_position < ps->pad_end_start) {
|
||||
start = samples_done - (ps->play_position + samples_done - ps->pad_end_start);
|
||||
}
|
||||
else {
|
||||
start = 0;
|
||||
}
|
||||
|
||||
memset(buf, 0, samples_to_do * sizeof(sample_t) * channels);
|
||||
return samples_to_do;
|
||||
memset(buf + (start * channels), 0, (samples_done - start) * sizeof(sample_t) * channels);
|
||||
return samples_done;
|
||||
}
|
||||
|
||||
|
||||
@ -445,7 +453,7 @@ int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
tmpbuf += done * vgmstream->pstate.output_channels; /* as if mixed */
|
||||
}
|
||||
|
||||
/* end padding (done before to avoid decoding if possible) */
|
||||
/* 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) {
|
||||
done = render_pad_end(vgmstream, tmpbuf, samples_to_do);
|
||||
@ -462,10 +470,16 @@ int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
|
||||
samples_done += done;
|
||||
|
||||
/* simple fadeout */
|
||||
if (!vgmstream->config.play_forever && ps->fade_left
|
||||
&& ps->play_position + done >= ps->fade_start) {
|
||||
render_fade(vgmstream, tmpbuf, done);
|
||||
if (!vgmstream->config.play_forever) {
|
||||
/* simple fadeout */
|
||||
if (ps->fade_left && ps->play_position + done >= ps->fade_start) {
|
||||
render_fade(vgmstream, tmpbuf, done);
|
||||
}
|
||||
|
||||
/* silence leftover buf samples (rarely used when no fade is set) */
|
||||
if (ps->play_position + done >= ps->pad_end_start) {
|
||||
render_pad_end(vgmstream, tmpbuf, done);
|
||||
}
|
||||
}
|
||||
|
||||
tmpbuf += done * vgmstream->pstate.output_channels;
|
||||
|
@ -508,6 +508,8 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = {
|
||||
init_vgmstream_xse_old,
|
||||
init_vgmstream_wady,
|
||||
init_vgmstream_dsp_sqex,
|
||||
init_vgmstream_dsp_wiivoice,
|
||||
init_vgmstream_xws,
|
||||
|
||||
/* 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 */
|
||||
|
@ -739,6 +739,7 @@ typedef enum {
|
||||
meta_SDRH,
|
||||
meta_WADY,
|
||||
meta_DSP_SQEX,
|
||||
meta_DSP_WIIVOICE,
|
||||
} meta_t;
|
||||
|
||||
/* standard WAVEFORMATEXTENSIBLE speaker positions */
|
||||
|
Loading…
x
Reference in New Issue
Block a user