mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 08:20:54 +01:00
Improve .xsb internal name reading and .xsb filename detection
This commit is contained in:
parent
50f8f53385
commit
19d5a6f82c
@ -312,6 +312,10 @@
|
|||||||
RelativePath=".\meta\xvag_streamfile.h"
|
RelativePath=".\meta\xvag_streamfile.h"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath=".\meta\xwb_xsb.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath=".\meta\xwma_konami_streamfile.h"
|
RelativePath=".\meta\xwma_konami_streamfile.h"
|
||||||
>
|
>
|
||||||
|
@ -90,6 +90,7 @@
|
|||||||
<ClInclude Include="coding\vorbis_custom_data_wwise.h" />
|
<ClInclude Include="coding\vorbis_custom_data_wwise.h" />
|
||||||
<ClInclude Include="coding\vorbis_custom_decoder.h" />
|
<ClInclude Include="coding\vorbis_custom_decoder.h" />
|
||||||
<ClInclude Include="meta\xvag_streamfile.h" />
|
<ClInclude Include="meta\xvag_streamfile.h" />
|
||||||
|
<ClInclude Include="meta\xwb_xsb.h" />
|
||||||
<ClInclude Include="meta\xwma_konami_streamfile.h" />
|
<ClInclude Include="meta\xwma_konami_streamfile.h" />
|
||||||
<ClInclude Include="meta\zsnd_streamfile.h" />
|
<ClInclude Include="meta\zsnd_streamfile.h" />
|
||||||
<ClInclude Include="mixing.h" />
|
<ClInclude Include="mixing.h" />
|
||||||
|
@ -179,6 +179,9 @@
|
|||||||
<ClInclude Include="meta\xvag_streamfile.h">
|
<ClInclude Include="meta\xvag_streamfile.h">
|
||||||
<Filter>meta\Header Files</Filter>
|
<Filter>meta\Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="meta\xwb_xsb.h">
|
||||||
|
<Filter>meta\Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="meta\xwma_konami_streamfile.h">
|
<ClInclude Include="meta\xwma_konami_streamfile.h">
|
||||||
<Filter>meta\Header Files</Filter>
|
<Filter>meta\Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
464
src/meta/xwb.c
464
src/meta/xwb.c
@ -1,7 +1,7 @@
|
|||||||
#include "meta.h"
|
#include "meta.h"
|
||||||
#include "../util.h"
|
|
||||||
#include "../coding/coding.h"
|
#include "../coding/coding.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include "xwb_xsb.h"
|
||||||
|
|
||||||
/* most info from XWBtool, xactwb.h, xact2wb.h and xact3wb.h */
|
/* most info from XWBtool, xactwb.h, xact2wb.h and xact3wb.h */
|
||||||
|
|
||||||
@ -68,6 +68,8 @@ typedef struct {
|
|||||||
uint32_t loop_start_sample;
|
uint32_t loop_start_sample;
|
||||||
uint32_t loop_end_sample;
|
uint32_t loop_end_sample;
|
||||||
|
|
||||||
|
char wavebank_name[64+1];
|
||||||
|
|
||||||
int is_crackdown;
|
int is_crackdown;
|
||||||
int fix_xma_num_samples;
|
int fix_xma_num_samples;
|
||||||
int fix_xma_loop_samples;
|
int fix_xma_loop_samples;
|
||||||
@ -79,7 +81,7 @@ static void get_name(char * buf, size_t maxsize, int target_subsong, xwb_header
|
|||||||
/* XWB - XACT Wave Bank (Microsoft SDK format for XBOX/XBOX360/Windows) */
|
/* XWB - XACT Wave Bank (Microsoft SDK format for XBOX/XBOX360/Windows) */
|
||||||
VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
|
VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
|
||||||
VGMSTREAM * vgmstream = NULL;
|
VGMSTREAM * vgmstream = NULL;
|
||||||
off_t start_offset, off, suboff;
|
off_t start_offset, offset, suboffset;
|
||||||
xwb_header xwb = {0};
|
xwb_header xwb = {0};
|
||||||
int target_subsong = streamFile->stream_index;
|
int target_subsong = streamFile->stream_index;
|
||||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
||||||
@ -88,7 +90,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
|
|||||||
/* checks */
|
/* checks */
|
||||||
/* .xwb: standard
|
/* .xwb: standard
|
||||||
* .xna: Touhou Makukasai ~ Fantasy Danmaku Festival (PC)
|
* .xna: Touhou Makukasai ~ Fantasy Danmaku Festival (PC)
|
||||||
* (extensionless): Grabbed by the Ghoulies (Xbox) */
|
* (extensionless): Ikaruga (X360/PC), Grabbed by the Ghoulies (Xbox) */
|
||||||
if (!check_extensions(streamFile,"xwb,xna,"))
|
if (!check_extensions(streamFile,"xwb,xna,"))
|
||||||
goto fail;
|
goto fail;
|
||||||
if ((read_32bitBE(0x00,streamFile) != 0x57424E44) && /* "WBND" (LE) */
|
if ((read_32bitBE(0x00,streamFile) != 0x57424E44) && /* "WBND" (LE) */
|
||||||
@ -115,7 +117,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
|
|||||||
/* read segment offsets (SEGIDX) */
|
/* read segment offsets (SEGIDX) */
|
||||||
if (xwb.version <= XACT1_0_MAX) {
|
if (xwb.version <= XACT1_0_MAX) {
|
||||||
xwb.total_subsongs = read_32bit(0x0c, streamFile);
|
xwb.total_subsongs = read_32bit(0x0c, streamFile);
|
||||||
/* 0x10: bank name (size 0x10) */
|
read_string(xwb.wavebank_name,0x10+1, 0x10, streamFile); /* null-terminated */
|
||||||
xwb.base_offset = 0;
|
xwb.base_offset = 0;
|
||||||
xwb.base_size = 0;
|
xwb.base_size = 0;
|
||||||
xwb.entry_offset = 0x50;
|
xwb.entry_offset = 0x50;
|
||||||
@ -131,68 +133,70 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
|
|||||||
xwb.extra_size = 0;
|
xwb.extra_size = 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
off = xwb.version <= XACT2_2_MAX ? 0x08 : 0x0c;
|
offset = xwb.version <= XACT2_2_MAX ? 0x08 : 0x0c;
|
||||||
xwb.base_offset = read_32bit(off+0x00, streamFile);//BANKDATA
|
xwb.base_offset = read_32bit(offset+0x00, streamFile);//BANKDATA
|
||||||
xwb.base_size = read_32bit(off+0x04, streamFile);
|
xwb.base_size = read_32bit(offset+0x04, streamFile);
|
||||||
xwb.entry_offset= read_32bit(off+0x08, streamFile);//ENTRYMETADATA
|
xwb.entry_offset= read_32bit(offset+0x08, streamFile);//ENTRYMETADATA
|
||||||
xwb.entry_size = read_32bit(off+0x0c, streamFile);
|
xwb.entry_size = read_32bit(offset+0x0c, streamFile);
|
||||||
|
|
||||||
/* read extra segments (values can be 0 == no segment) */
|
/* read extra segments (values can be 0 == no segment) */
|
||||||
if (xwb.version <= XACT1_1_MAX) {
|
if (xwb.version <= XACT1_1_MAX) {
|
||||||
xwb.names_offset = read_32bit(off+0x10, streamFile);//ENTRYNAMES
|
xwb.names_offset = read_32bit(offset+0x10, streamFile);//ENTRYNAMES
|
||||||
xwb.names_size = read_32bit(off+0x14, streamFile);
|
xwb.names_size = read_32bit(offset+0x14, streamFile);
|
||||||
xwb.names_entry_size= 0x40;
|
xwb.names_entry_size= 0x40;
|
||||||
xwb.extra_offset = 0;
|
xwb.extra_offset = 0;
|
||||||
xwb.extra_size = 0;
|
xwb.extra_size = 0;
|
||||||
suboff = 0x04*2;
|
suboffset = 0x04*2;
|
||||||
}
|
}
|
||||||
else if (xwb.version <= XACT2_1_MAX) {
|
else if (xwb.version <= XACT2_1_MAX) {
|
||||||
xwb.names_offset = read_32bit(off+0x10, streamFile);//ENTRYNAMES
|
xwb.names_offset = read_32bit(offset+0x10, streamFile);//ENTRYNAMES
|
||||||
xwb.names_size = read_32bit(off+0x14, streamFile);
|
xwb.names_size = read_32bit(offset+0x14, streamFile);
|
||||||
xwb.names_entry_size= 0x40;
|
xwb.names_entry_size= 0x40;
|
||||||
xwb.extra_offset = read_32bit(off+0x18, streamFile);//EXTRA
|
xwb.extra_offset = read_32bit(offset+0x18, streamFile);//EXTRA
|
||||||
xwb.extra_size = read_32bit(off+0x1c, streamFile);
|
xwb.extra_size = read_32bit(offset+0x1c, streamFile);
|
||||||
suboff = 0x04*2 + 0x04*2;
|
suboffset = 0x04*2 + 0x04*2;
|
||||||
} else {
|
} else {
|
||||||
xwb.extra_offset = read_32bit(off+0x10, streamFile);//SEEKTABLES
|
xwb.extra_offset = read_32bit(offset+0x10, streamFile);//SEEKTABLES
|
||||||
xwb.extra_size = read_32bit(off+0x14, streamFile);
|
xwb.extra_size = read_32bit(offset+0x14, streamFile);
|
||||||
xwb.names_offset = read_32bit(off+0x18, streamFile);//ENTRYNAMES
|
xwb.names_offset = read_32bit(offset+0x18, streamFile);//ENTRYNAMES
|
||||||
xwb.names_size = read_32bit(off+0x1c, streamFile);
|
xwb.names_size = read_32bit(offset+0x1c, streamFile);
|
||||||
xwb.names_entry_size= 0x40;
|
xwb.names_entry_size= 0x40;
|
||||||
suboff = 0x04*2 + 0x04*2;
|
suboffset = 0x04*2 + 0x04*2;
|
||||||
}
|
}
|
||||||
|
|
||||||
xwb.data_offset = read_32bit(off+0x10+suboff, streamFile);//ENTRYWAVEDATA
|
xwb.data_offset = read_32bit(offset+0x10+suboffset, streamFile);//ENTRYWAVEDATA
|
||||||
xwb.data_size = read_32bit(off+0x14+suboff, streamFile);
|
xwb.data_size = read_32bit(offset+0x14+suboffset, streamFile);
|
||||||
|
|
||||||
/* for Techland's XWB with no data */
|
/* for Techland's XWB with no data */
|
||||||
if (xwb.base_offset == 0) goto fail;
|
if (xwb.base_offset == 0) goto fail;
|
||||||
|
|
||||||
/* read base entry (WAVEBANKDATA) */
|
/* read base entry (WAVEBANKDATA) */
|
||||||
off = xwb.base_offset;
|
offset = xwb.base_offset;
|
||||||
xwb.base_flags = (uint32_t)read_32bit(off+0x00, streamFile);
|
xwb.base_flags = (uint32_t)read_32bit(offset+0x00, streamFile);
|
||||||
xwb.total_subsongs = read_32bit(off+0x04, streamFile);
|
xwb.total_subsongs = read_32bit(offset+0x04, streamFile);
|
||||||
/* 0x08: bank name (size 0x40) */
|
read_string(xwb.wavebank_name,0x40+1, offset+0x08, streamFile); /* null-terminated */
|
||||||
suboff = 0x08 + (xwb.version <= XACT1_1_MAX ? 0x10 : 0x40);
|
suboffset = 0x08 + (xwb.version <= XACT1_1_MAX ? 0x10 : 0x40);
|
||||||
xwb.entry_elem_size = read_32bit(off+suboff+0x00, streamFile);
|
xwb.entry_elem_size = read_32bit(offset+suboffset+0x00, streamFile);
|
||||||
/* suboff+0x04: meta name entry size */
|
/* suboff+0x04: meta name entry size */
|
||||||
xwb.entry_alignment = read_32bit(off+suboff+0x08, streamFile); /* usually 1 dvd sector */
|
xwb.entry_alignment = read_32bit(offset+suboffset+0x08, streamFile); /* usually 1 dvd sector */
|
||||||
xwb.format = read_32bit(off+suboff+0x0c, streamFile); /* compact mode only */
|
xwb.format = read_32bit(offset+suboffset+0x0c, streamFile); /* compact mode only */
|
||||||
/* suboff+0x10: build time 64b (XACT2/3) */
|
/* suboff+0x10: build time 64b (XACT2/3) */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//;VGM_LOG("XWB: wavebank name='%s'\n", xwb.wavebank_name);
|
||||||
|
|
||||||
if (target_subsong == 0) target_subsong = 1; /* auto: default to 1 */
|
if (target_subsong == 0) target_subsong = 1; /* auto: default to 1 */
|
||||||
if (target_subsong < 0 || target_subsong > xwb.total_subsongs || xwb.total_subsongs < 1) goto fail;
|
if (target_subsong < 0 || target_subsong > xwb.total_subsongs || xwb.total_subsongs < 1) goto fail;
|
||||||
|
|
||||||
|
|
||||||
/* read stream entry (WAVEBANKENTRY) */
|
/* read stream entry (WAVEBANKENTRY) */
|
||||||
off = xwb.entry_offset + (target_subsong-1) * xwb.entry_elem_size;
|
offset = xwb.entry_offset + (target_subsong-1) * xwb.entry_elem_size;
|
||||||
|
|
||||||
if (xwb.base_flags & WAVEBANK_FLAGS_COMPACT) { /* compact entry [NFL Fever 2004 demo from Amped 2 (Xbox)] */
|
if (xwb.base_flags & WAVEBANK_FLAGS_COMPACT) { /* compact entry [NFL Fever 2004 demo from Amped 2 (Xbox)] */
|
||||||
uint32_t entry, size_deviation, sector_offset;
|
uint32_t entry, size_deviation, sector_offset;
|
||||||
off_t next_stream_offset;
|
off_t next_stream_offset;
|
||||||
|
|
||||||
entry = (uint32_t)read_32bit(off+0x00, streamFile);
|
entry = (uint32_t)read_32bit(offset+0x00, streamFile);
|
||||||
size_deviation = ((entry >> 21) & 0x7FF); /* 11b, padding data for sector alignment in bytes*/
|
size_deviation = ((entry >> 21) & 0x7FF); /* 11b, padding data for sector alignment in bytes*/
|
||||||
sector_offset = (entry & 0x1FFFFF); /* 21b, offset within data in sectors */
|
sector_offset = (entry & 0x1FFFFF); /* 21b, offset within data in sectors */
|
||||||
|
|
||||||
@ -200,7 +204,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
|
|||||||
|
|
||||||
/* find size using next offset */
|
/* find size using next offset */
|
||||||
if (target_subsong < xwb.total_subsongs) {
|
if (target_subsong < xwb.total_subsongs) {
|
||||||
uint32_t next_entry = (uint32_t)read_32bit(off+0x04, streamFile);
|
uint32_t next_entry = (uint32_t)read_32bit(offset+0x04, streamFile);
|
||||||
next_stream_offset = xwb.data_offset + (next_entry & 0x1FFFFF)*xwb.entry_alignment;
|
next_stream_offset = xwb.data_offset + (next_entry & 0x1FFFFF)*xwb.entry_alignment;
|
||||||
}
|
}
|
||||||
else { /* for last entry (or first, when subsongs = 1) */
|
else { /* for last entry (or first, when subsongs = 1) */
|
||||||
@ -209,31 +213,31 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
|
|||||||
xwb.stream_size = next_stream_offset - xwb.stream_offset - size_deviation;
|
xwb.stream_size = next_stream_offset - xwb.stream_offset - size_deviation;
|
||||||
}
|
}
|
||||||
else if (xwb.version <= XACT1_0_MAX) {
|
else if (xwb.version <= XACT1_0_MAX) {
|
||||||
xwb.format = (uint32_t)read_32bit(off+0x00, streamFile);
|
xwb.format = (uint32_t)read_32bit(offset+0x00, streamFile);
|
||||||
xwb.stream_offset = xwb.data_offset + (uint32_t)read_32bit(off+0x04, streamFile);
|
xwb.stream_offset = xwb.data_offset + (uint32_t)read_32bit(offset+0x04, streamFile);
|
||||||
xwb.stream_size = (uint32_t)read_32bit(off+0x08, streamFile);
|
xwb.stream_size = (uint32_t)read_32bit(offset+0x08, streamFile);
|
||||||
|
|
||||||
xwb.loop_start = (uint32_t)read_32bit(off+0x0c, streamFile);
|
xwb.loop_start = (uint32_t)read_32bit(offset+0x0c, streamFile);
|
||||||
xwb.loop_end = (uint32_t)read_32bit(off+0x10, streamFile);//length
|
xwb.loop_end = (uint32_t)read_32bit(offset+0x10, streamFile);//length
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
uint32_t entry_info = (uint32_t)read_32bit(off+0x00, streamFile);
|
uint32_t entry_info = (uint32_t)read_32bit(offset+0x00, streamFile);
|
||||||
if (xwb.version <= XACT1_1_MAX) {
|
if (xwb.version <= XACT1_1_MAX) {
|
||||||
xwb.entry_flags = entry_info;
|
xwb.entry_flags = entry_info;
|
||||||
} else {
|
} else {
|
||||||
xwb.entry_flags = (entry_info) & 0xF; /*4b*/
|
xwb.entry_flags = (entry_info) & 0xF; /*4b*/
|
||||||
xwb.num_samples = (entry_info >> 4) & 0x0FFFFFFF; /*28b*/
|
xwb.num_samples = (entry_info >> 4) & 0x0FFFFFFF; /*28b*/
|
||||||
}
|
}
|
||||||
xwb.format = (uint32_t)read_32bit(off+0x04, streamFile);
|
xwb.format = (uint32_t)read_32bit(offset+0x04, streamFile);
|
||||||
xwb.stream_offset = xwb.data_offset + (uint32_t)read_32bit(off+0x08, streamFile);
|
xwb.stream_offset = xwb.data_offset + (uint32_t)read_32bit(offset+0x08, streamFile);
|
||||||
xwb.stream_size = (uint32_t)read_32bit(off+0x0c, streamFile);
|
xwb.stream_size = (uint32_t)read_32bit(offset+0x0c, streamFile);
|
||||||
|
|
||||||
if (xwb.version <= XACT2_1_MAX) { /* LoopRegion (bytes) */
|
if (xwb.version <= XACT2_1_MAX) { /* LoopRegion (bytes) */
|
||||||
xwb.loop_start = (uint32_t)read_32bit(off+0x10, streamFile);
|
xwb.loop_start = (uint32_t)read_32bit(offset+0x10, streamFile);
|
||||||
xwb.loop_end = (uint32_t)read_32bit(off+0x14, streamFile);//length (LoopRegion) or offset (XMALoopRegion in late XACT2)
|
xwb.loop_end = (uint32_t)read_32bit(offset+0x14, streamFile);//length (LoopRegion) or offset (XMALoopRegion in late XACT2)
|
||||||
} else { /* LoopRegion (samples) */
|
} else { /* LoopRegion (samples) */
|
||||||
xwb.loop_start_sample = (uint32_t)read_32bit(off+0x10, streamFile);
|
xwb.loop_start_sample = (uint32_t)read_32bit(offset+0x10, streamFile);
|
||||||
xwb.loop_end_sample = (uint32_t)read_32bit(off+0x14, streamFile) + xwb.loop_start_sample;
|
xwb.loop_end_sample = (uint32_t)read_32bit(offset+0x14, streamFile) + xwb.loop_start_sample;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -572,6 +576,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
|
|||||||
temp_vgmstream->num_streams = vgmstream->num_streams;
|
temp_vgmstream->num_streams = vgmstream->num_streams;
|
||||||
temp_vgmstream->stream_size = vgmstream->stream_size;
|
temp_vgmstream->stream_size = vgmstream->stream_size;
|
||||||
temp_vgmstream->meta_type = vgmstream->meta_type;
|
temp_vgmstream->meta_type = vgmstream->meta_type;
|
||||||
|
strcpy(temp_vgmstream->stream_name, vgmstream->stream_name);
|
||||||
|
|
||||||
close_vgmstream(vgmstream);
|
close_vgmstream(vgmstream);
|
||||||
return temp_vgmstream;
|
return temp_vgmstream;
|
||||||
@ -596,7 +601,6 @@ fail:
|
|||||||
|
|
||||||
/* ****************************************************************************** */
|
/* ****************************************************************************** */
|
||||||
|
|
||||||
/* try to get the stream name in the .xwb, though they are very rarely included */
|
|
||||||
static int get_xwb_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamFile) {
|
static int get_xwb_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamFile) {
|
||||||
size_t read;
|
size_t read;
|
||||||
|
|
||||||
@ -612,363 +616,45 @@ fail:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ****************************************************************************** */
|
static int get_xsb_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamFile) {
|
||||||
/* XSB parsing from xwb_split (mostly untouched), could be improved */
|
|
||||||
|
|
||||||
#define XSB_XACT1_MAX 11
|
|
||||||
#define XSB_XACT2_MAX 41
|
|
||||||
|
|
||||||
/**
|
|
||||||
* XWB contain stream info (channels, loop, data etc), often from multiple streams.
|
|
||||||
* XSBs contain info about how to play sounds (volume, pitch, name, etc) from XWBs (music or SFX).
|
|
||||||
* We only need to parse the XSB for the stream names.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
int sound_count;
|
|
||||||
} xsb_wavebank;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int stream_index; /* stream id in the xwb (doesn't need to match xsb sound order) */
|
|
||||||
int wavebank; /* xwb id, if the xsb has multiple wavebanks */
|
|
||||||
off_t name_index; /* name order */
|
|
||||||
off_t name_offset; /* global offset to the name string */
|
|
||||||
off_t sound_offset; /* global offset to the xsb sound */
|
|
||||||
off_t unk_index; /* some kind of number up to sound_count or 0xffff */
|
|
||||||
} xsb_sound;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
/* XSB header info */
|
|
||||||
xsb_sound * xsb_sounds; /* array of sounds info from the xsb, simplified */
|
|
||||||
xsb_wavebank * xsb_wavebanks; /* array of wavebank info from the xsb, simplified */
|
|
||||||
|
|
||||||
off_t xsb_sounds_offset;
|
|
||||||
size_t xsb_sounds_count;
|
|
||||||
|
|
||||||
size_t xsb_simple_sounds_offset; /* sound cues */
|
|
||||||
size_t xsb_simple_sounds_count;
|
|
||||||
size_t xsb_complex_sounds_offset;
|
|
||||||
size_t xsb_complex_sounds_count;
|
|
||||||
|
|
||||||
size_t xsb_wavebanks_count;
|
|
||||||
off_t xsb_nameoffsets_offset;
|
|
||||||
} xsb_header;
|
|
||||||
|
|
||||||
|
|
||||||
/* try to find the stream name in a companion XSB file, a comically complex cue format. */
|
|
||||||
static int get_xsb_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamXwb, char* filename) {
|
|
||||||
STREAMFILE *streamFile = NULL;
|
|
||||||
int i,j, start_sound, cfg__start_sound = 0, cfg__selected_wavebank = 0;
|
|
||||||
int xsb_version;
|
|
||||||
off_t off, suboff, name_offset = 0;
|
|
||||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
|
||||||
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
|
|
||||||
xsb_header xsb = {0};
|
xsb_header xsb = {0};
|
||||||
|
|
||||||
|
xsb.selected_stream = target_subsong - 1;
|
||||||
if (filename)
|
if (!parse_xsb(&xsb, streamFile, xwb->wavebank_name))
|
||||||
streamFile = open_streamfile_by_filename(streamXwb, filename);
|
|
||||||
else
|
|
||||||
streamFile = open_streamfile_by_ext(streamXwb, "xsb");
|
|
||||||
if (!streamFile) goto fail;
|
|
||||||
|
|
||||||
/* check header */
|
|
||||||
if ((read_32bitBE(0x00,streamFile) != 0x5344424B) && /* "SDBK" (LE) */
|
|
||||||
(read_32bitBE(0x00,streamFile) != 0x4B424453)) /* "KBDS" (BE) */
|
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
if (read_32bitBE(0x00,streamFile) == 0x5344424B) { /* SDBK */
|
if ((xwb->version <= XACT1_1_MAX && xsb.version > XSB_XACT1_2_MAX) ||
|
||||||
read_32bit = read_32bitLE;
|
(xwb->version <= XACT2_2_MAX && xsb.version > XSB_XACT2_MAX)) {
|
||||||
read_16bit = read_16bitLE;
|
VGM_LOG("XSB: mismatched XACT versions: xsb v%i vs xwb v%i\n", xsb.version, xwb->version);
|
||||||
} else {
|
|
||||||
read_32bit = read_32bitBE;
|
|
||||||
read_16bit = read_16bitBE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* read main header (SoundBankHeader) */
|
|
||||||
xsb_version = read_16bit(0x04, streamFile);
|
|
||||||
if ((xwb->version <= XACT1_1_MAX && xsb_version > XSB_XACT1_MAX) || (xwb->version <= XACT2_2_MAX && xsb_version > XSB_XACT2_MAX)) {
|
|
||||||
VGM_LOG("XSB: xsb and xwb are from different XACT versions (xsb v%i vs xwb v%i)\n", xsb_version, xwb->version);
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB: name found=%i at %lx\n", xsb.parse_found, xsb.name_offset);
|
||||||
off = 0;
|
if (!xsb.parse_found || xsb.name_offset == 0)
|
||||||
if (xsb_version <= XSB_XACT1_MAX) {
|
|
||||||
xsb.xsb_wavebanks_count = 1; //(uint8_t)read_8bit(0x22, streamFile);
|
|
||||||
xsb.xsb_sounds_count = (uint16_t)read_16bit(0x1e, streamFile);//@ 0x1a? 0x1c?
|
|
||||||
//xsb.xsb_names_size = 0;
|
|
||||||
//xsb.xsb_names_offset = 0;
|
|
||||||
xsb.xsb_nameoffsets_offset = 0;
|
|
||||||
xsb.xsb_sounds_offset = 0x38;
|
|
||||||
} else if (xsb_version <= XSB_XACT2_MAX) {
|
|
||||||
xsb.xsb_simple_sounds_count = (uint16_t)read_16bit(0x09, streamFile);
|
|
||||||
xsb.xsb_complex_sounds_count = (uint16_t)read_16bit(0x0B, streamFile);
|
|
||||||
xsb.xsb_wavebanks_count = (uint8_t)read_8bit(0x11, streamFile);
|
|
||||||
xsb.xsb_sounds_count = (uint16_t)read_16bit(0x12, streamFile);
|
|
||||||
//0x14: 16b unk
|
|
||||||
//xsb.xsb_names_size = read_32bit(0x16, streamFile);
|
|
||||||
xsb.xsb_simple_sounds_offset = read_32bit(0x1a, streamFile);
|
|
||||||
xsb.xsb_complex_sounds_offset = read_32bit(0x1e, streamFile); //todo 0x1e?
|
|
||||||
//xsb.xsb_names_offset = read_32bit(0x22, streamFile);
|
|
||||||
xsb.xsb_nameoffsets_offset = read_32bit(0x3a, streamFile);
|
|
||||||
xsb.xsb_sounds_offset = read_32bit(0x3e, streamFile);
|
|
||||||
} else {
|
|
||||||
xsb.xsb_simple_sounds_count = (uint16_t)read_16bit(0x13, streamFile);
|
|
||||||
xsb.xsb_complex_sounds_count = (uint16_t)read_16bit(0x15, streamFile);
|
|
||||||
xsb.xsb_wavebanks_count = (uint8_t)read_8bit(0x1b, streamFile);
|
|
||||||
xsb.xsb_sounds_count = read_16bit(0x1c, streamFile);
|
|
||||||
//xsb.xsb_names_size = read_32bit(0x1e, streamFile);
|
|
||||||
xsb.xsb_simple_sounds_offset = read_32bit(0x22, streamFile);
|
|
||||||
xsb.xsb_complex_sounds_offset = read_32bit(0x26, streamFile);
|
|
||||||
//xsb.xsb_names_offset = read_32bit(0x2a, streamFile);
|
|
||||||
xsb.xsb_nameoffsets_offset = read_32bit(0x42, streamFile);
|
|
||||||
xsb.xsb_sounds_offset = read_32bit(0x46, streamFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
VGM_ASSERT(xsb.xsb_sounds_count < xwb->total_subsongs,
|
|
||||||
"XSB: number of streams in xsb lower than xwb (xsb %i vs xwb %i)\n", xsb.xsb_sounds_count, xwb->total_subsongs);
|
|
||||||
|
|
||||||
VGM_ASSERT(xsb.xsb_simple_sounds_count + xsb.xsb_complex_sounds_count != xsb.xsb_sounds_count,
|
|
||||||
"XSB: number of xsb sounds doesn't match simple + complex sounds (simple %i, complex %i, total %i)\n", xsb.xsb_simple_sounds_count, xsb.xsb_complex_sounds_count, xsb.xsb_sounds_count);
|
|
||||||
|
|
||||||
|
|
||||||
/* init stuff */
|
|
||||||
xsb.xsb_sounds = calloc(xsb.xsb_sounds_count, sizeof(xsb_sound));
|
|
||||||
if (!xsb.xsb_sounds) goto fail;
|
|
||||||
|
|
||||||
xsb.xsb_wavebanks = calloc(xsb.xsb_wavebanks_count, sizeof(xsb_wavebank));
|
|
||||||
if (!xsb.xsb_wavebanks) goto fail;
|
|
||||||
|
|
||||||
/* The following is a bizarre soup of flags, tables, offsets to offsets and stuff, just to get the actual name. Info:
|
|
||||||
* - https://wiki.multimedia.cx/index.php/XACT
|
|
||||||
* - https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Audio/Xact/SoundBank.cs*/
|
|
||||||
|
|
||||||
/* parse xsb sounds */
|
|
||||||
off = xsb.xsb_sounds_offset;
|
|
||||||
for (i = 0; i < xsb.xsb_sounds_count; i++) {
|
|
||||||
xsb_sound *s = &(xsb.xsb_sounds[i]);
|
|
||||||
uint32_t flag;
|
|
||||||
size_t size;
|
|
||||||
|
|
||||||
if (xsb_version <= XSB_XACT1_MAX) {
|
|
||||||
/* The format seems constant */
|
|
||||||
flag = read_8bit(off+0x00, streamFile);
|
|
||||||
size = 0x14;
|
|
||||||
|
|
||||||
if (flag != 0x01) {
|
|
||||||
//VGM_LOG("XSB: xsb flag 0x%x at offset 0x%08lx not implemented\n", flag, off);
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
|
||||||
|
|
||||||
s->wavebank = 0; //(uint8_t)read_8bit(off+suboff + 0x02, streamFile);
|
|
||||||
s->stream_index = (uint16_t)read_16bit(off+0x02, streamFile);
|
|
||||||
s->sound_offset = off;
|
|
||||||
s->name_offset = (uint16_t)read_16bit(off+0x04, streamFile);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* Each XSB sound has a variable size and somewhere inside is the stream/wavebank index.
|
|
||||||
* Various flags control the sound layout, but I can't make sense of them so quick hack instead */
|
|
||||||
flag = read_8bit(off+0x00, streamFile);
|
|
||||||
//0x01 16b unk, 0x03: 8b unk 04: 16b unk, 06: 8b unk
|
|
||||||
size = (uint16_t)read_16bit(off+0x07, streamFile);
|
|
||||||
|
|
||||||
if (!(flag & 0x01)) { /* simple sound */
|
|
||||||
suboff = 0x09;
|
|
||||||
} else { /* complex sound */
|
|
||||||
/* not very exact but seems to work */
|
|
||||||
if (flag==0x01 || flag==0x03 || flag==0x05 || flag==0x07) {
|
|
||||||
if (size == 0x49) { //grotesque hack for Eschatos (these flags are way too complex)
|
|
||||||
suboff = 0x23;
|
|
||||||
} else if (size % 2 == 1 && (uint16_t)read_16bit(off+size-0x2, streamFile)!=0) {
|
|
||||||
suboff = size - 0x08 - 0x07; //7 unk bytes at the end
|
|
||||||
} else {
|
|
||||||
suboff = size - 0x08;
|
|
||||||
}
|
|
||||||
//} else if (flag==0x11) { /* Stardew Valley (Switch) */
|
|
||||||
// suboff = size; //???
|
|
||||||
} else {
|
|
||||||
//VGM_LOG("XSB: xsb flag 0x%x (size=%x) at offset 0x%08lx not implemented\n", flag, size, off);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s->stream_index = (uint16_t)read_16bit(off+suboff + 0x00, streamFile);
|
|
||||||
s->wavebank = (uint8_t)read_8bit(off+suboff + 0x02, streamFile);
|
|
||||||
s->sound_offset = off;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s->wavebank+1 > xsb.xsb_wavebanks_count) {
|
|
||||||
//VGM_LOG("XSB: unknown xsb wavebank id %i at offset 0x%lx\n", s->wavebank, off);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
xsb.xsb_wavebanks[s->wavebank].sound_count += 1;
|
|
||||||
off += size;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* parse name offsets */
|
|
||||||
if (xsb_version > XSB_XACT1_MAX) {
|
|
||||||
/* "cue" name order: first simple sounds, then complex sounds
|
|
||||||
* Both aren't ordered like the sound entries, instead use a global offset to the entry
|
|
||||||
*
|
|
||||||
* ex. of a possible XSB:
|
|
||||||
* name 1 = simple sound 1 > sound entry 2 (points to xwb stream 4): stream 4 uses name 1
|
|
||||||
* name 2 = simple sound 2 > sound entry 1 (points to xwb stream 1): stream 1 uses name 2
|
|
||||||
* name 3 = complex sound 1 > sound entry 3 (points to xwb stream 3): stream 3 uses name 3
|
|
||||||
* name 4 = complex sound 2 > sound entry 4 (points to xwb stream 2): stream 2 uses name 4
|
|
||||||
*
|
|
||||||
* Multiple cues can point to the same sound entry but we only use the first name (meaning some won't be used) */
|
|
||||||
off_t n_off = xsb.xsb_nameoffsets_offset;
|
|
||||||
|
|
||||||
off = xsb.xsb_simple_sounds_offset;
|
|
||||||
for (i = 0; i < xsb.xsb_simple_sounds_count; i++) {
|
|
||||||
off_t sound_offset = read_32bit(off + 0x01, streamFile);
|
|
||||||
off += 0x05;
|
|
||||||
|
|
||||||
/* find sound by offset */
|
|
||||||
for (j = 0; j < xsb.xsb_sounds_count; j++) {
|
|
||||||
xsb_sound *s = &(xsb.xsb_sounds[j]);;
|
|
||||||
/* update with the current name offset */
|
|
||||||
if (!s->name_offset && sound_offset == s->sound_offset) {
|
|
||||||
s->name_offset = read_32bit(n_off + 0x00, streamFile);
|
|
||||||
s->unk_index = read_16bit(n_off + 0x04, streamFile);
|
|
||||||
n_off += 0x06;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
off = xsb.xsb_complex_sounds_offset;
|
|
||||||
for (i = 0; i < xsb.xsb_complex_sounds_count; i++) {
|
|
||||||
off_t sound_offset = read_32bit(off + 0x01, streamFile);
|
|
||||||
off += 0x0f;
|
|
||||||
|
|
||||||
/* find sound by offset */
|
|
||||||
for (j = 0; j < xsb.xsb_sounds_count; j++) {
|
|
||||||
xsb_sound *s = &(xsb.xsb_sounds[j]);;
|
|
||||||
/* update with the current name offset */
|
|
||||||
if (!s->name_offset && sound_offset == s->sound_offset) {
|
|
||||||
s->name_offset = read_32bit(n_off + 0x00, streamFile);
|
|
||||||
s->unk_index = read_16bit(n_off + 0x04, streamFile);
|
|
||||||
n_off += 0x06;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: it's possible to find the wavebank using the name
|
|
||||||
/* try to find correct wavebank, in cases of multiple */
|
|
||||||
if (!cfg__selected_wavebank) {
|
|
||||||
for (i = 0; i < xsb.xsb_wavebanks_count; i++) {
|
|
||||||
xsb_wavebank *w = &(xsb.xsb_wavebanks[i]);
|
|
||||||
|
|
||||||
//CHECK_EXIT(w->sound_count == 0, "ERROR: xsb wavebank %i has no sounds", i); //Ikaruga PC
|
|
||||||
|
|
||||||
if (w->sound_count == xwb->total_subsongs) {
|
|
||||||
if (!cfg__selected_wavebank) {
|
|
||||||
//VGM_LOG("XSB: multiple xsb wavebanks with the same number of sounds, use -w to specify one of the wavebanks\n");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg__selected_wavebank = i+1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* banks with different number of sounds but only one wavebank, just select the first */
|
|
||||||
if (!cfg__selected_wavebank && xsb.xsb_wavebanks_count==1) {
|
|
||||||
cfg__selected_wavebank = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cfg__selected_wavebank) {
|
|
||||||
//VGM_LOG("XSB: multiple xsb wavebanks but autodetect didn't work\n");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
if (xsb.xsb_wavebanks[cfg__selected_wavebank-1].sound_count == 0) {
|
|
||||||
//VGM_LOG("XSB: xsb selected wavebank %i has no sounds\n", cfg__selected_wavebank);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cfg__start_sound) {
|
|
||||||
if (xsb.xsb_wavebanks[cfg__selected_wavebank-1].sound_count - (cfg__start_sound-1) < xwb->total_subsongs) {
|
|
||||||
//VGM_LOG("XSB: starting sound too high (max in selected wavebank is %i)\n", xsb.xsb_wavebanks[cfg__selected_wavebank-1].sound_count - xwb->total_subsongs + 1);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
if (!cfg->ignore_names_not_found)
|
|
||||||
CHECK_EXIT(xwb->xsb_wavebanks[cfg->selected_wavebank-1].sound_count > xwb->streams_count, "ERROR: number of streams in xsb wavebank bigger than xwb (xsb %i vs xwb %i), use -s to specify (1=first)", xwb->xsb_wavebanks[cfg->selected_wavebank-1].sound_count, xwb->total_subsongs);
|
|
||||||
if (!cfg->ignore_names_not_found)
|
|
||||||
CHECK_EXIT(xwb->xsb_wavebanks[cfg->selected_wavebank-1].sound_count < xwb->streams_count, "ERROR: number of streams in xsb wavebank lower than xwb (xsb %i vs xwb %i), use -n to ignore (some names won't be extracted)", xwb->xsb_wavebanks[cfg->selected_wavebank-1].sound_count, xwb->total_subsongs);
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
//if (!cfg->ignore_names_not_found)
|
|
||||||
// CHECK_EXIT(xwb->xsb_wavebanks[cfg->selected_wavebank-1].sound_count != xwb->streams_count, "ERROR: number of streams in xsb wavebank different than xwb (xsb %i vs xwb %i), use -s to specify (1=first)", xwb->xsb_wavebanks[cfg->selected_wavebank-1].sound_count, xwb->total_subsongs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* *************************** */
|
|
||||||
|
|
||||||
start_sound = cfg__start_sound ? cfg__start_sound-1 : 0;
|
|
||||||
|
|
||||||
/* get name offset */
|
|
||||||
for (i = start_sound; i < xsb.xsb_sounds_count; i++) {
|
|
||||||
xsb_sound *s = &(xsb.xsb_sounds[i]);
|
|
||||||
if (s->wavebank == cfg__selected_wavebank-1
|
|
||||||
&& s->stream_index == target_subsong-1){
|
|
||||||
name_offset = s->name_offset;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name_offset)
|
|
||||||
read_string(buf,maxsize, name_offset,streamFile);
|
|
||||||
|
|
||||||
//return; /* no return, let free */
|
|
||||||
|
|
||||||
|
read_string(buf,maxsize, xsb.name_offset,streamFile); /* null-terminated */
|
||||||
|
return 1;
|
||||||
fail:
|
fail:
|
||||||
free(xsb.xsb_sounds);
|
return 0;
|
||||||
free(xsb.xsb_wavebanks);
|
|
||||||
close_streamfile(streamFile);
|
|
||||||
|
|
||||||
return (name_offset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void get_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamFile) {
|
static void get_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamXwb) {
|
||||||
|
STREAMFILE *streamXsb = NULL;
|
||||||
int name_found;
|
int name_found;
|
||||||
char xwb_filename[PATH_LIMIT];
|
|
||||||
char xsb_filename[PATH_LIMIT];
|
|
||||||
|
|
||||||
/* try inside this xwb */
|
/* try to get the stream name in the .xwb, though they are very rarely included */
|
||||||
name_found = get_xwb_name(buf, maxsize, target_subsong, xwb, streamFile);
|
name_found = get_xwb_name(buf, maxsize, target_subsong, xwb, streamXwb);
|
||||||
if (name_found) return;
|
if (name_found) return;
|
||||||
|
|
||||||
|
/* try again in a companion .xsb file, a comically complex cue format */
|
||||||
|
streamXsb = open_xsb_filename_pair(streamXwb);
|
||||||
|
if (!streamXsb) return; /* not all xwb have xsb though */
|
||||||
|
|
||||||
/* try again in external .xsb, using a bunch of possible name pairs */
|
name_found = get_xsb_name(buf, maxsize, target_subsong, xwb, streamXsb);
|
||||||
get_streamfile_filename(streamFile,xwb_filename,PATH_LIMIT);
|
close_streamfile(streamXsb);
|
||||||
|
|
||||||
if (strcmp(xwb_filename,"Wave Bank.xwb")==0) {
|
if (!name_found) {
|
||||||
strcpy(xsb_filename,"Sound Bank.xsb");
|
buf[0] = '\0';
|
||||||
}
|
}
|
||||||
else if (strcmp(xwb_filename,"UIMusicBank.xwb")==0) {
|
|
||||||
strcpy(xsb_filename,"UISoundBank.xsb");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
xsb_filename[0] = '\0';
|
|
||||||
}
|
|
||||||
//todo try others: InGameMusic.xwb + ingamemusic.xsb, NB_BGM_m0100_WB.xwb + NB_BGM_m0100_SB.xsb, etc
|
|
||||||
|
|
||||||
if (xsb_filename[0] != '\0') {
|
|
||||||
name_found = get_xsb_name(buf, maxsize, target_subsong, xwb, streamFile, xsb_filename);
|
|
||||||
if (name_found) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* one last time with same name */
|
|
||||||
get_xsb_name(buf, maxsize, target_subsong, xwb, streamFile, NULL);
|
|
||||||
}
|
}
|
||||||
|
836
src/meta/xwb_xsb.h
Normal file
836
src/meta/xwb_xsb.h
Normal file
@ -0,0 +1,836 @@
|
|||||||
|
#ifndef _XWB_XSB_H_
|
||||||
|
#define _XWB_XSB_H_
|
||||||
|
|
||||||
|
#define XSB_XACT1_0_MAX 5 /* Unreal Championship (Xbox) */
|
||||||
|
#define XSB_XACT1_1_MAX 8 /* Die Hard: Vendetta (Xbox) */
|
||||||
|
#define XSB_XACT1_2_MAX 11 /* other Xbox games */
|
||||||
|
#define XSB_XACT2_MAX 41 /* other PC/X360 games */
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/* config */
|
||||||
|
int selected_stream;
|
||||||
|
int selected_wavebank;
|
||||||
|
|
||||||
|
/* state */
|
||||||
|
int big_endian;
|
||||||
|
int version;
|
||||||
|
|
||||||
|
int simple_cues_count;
|
||||||
|
off_t simple_cues_offset;
|
||||||
|
int complex_cues_count;
|
||||||
|
off_t complex_cues_offset;
|
||||||
|
int sounds_count;
|
||||||
|
off_t sounds_offset;
|
||||||
|
int wavebanks_count;
|
||||||
|
off_t wavebanks_offset;
|
||||||
|
int wavebanks_name_size;
|
||||||
|
off_t nameoffsets_offset;
|
||||||
|
int cue_names_size;
|
||||||
|
off_t cue_names_offset;
|
||||||
|
|
||||||
|
/* output */
|
||||||
|
int parse_found;
|
||||||
|
int parse_done;
|
||||||
|
off_t name_offset;
|
||||||
|
|
||||||
|
} xsb_header;
|
||||||
|
|
||||||
|
|
||||||
|
static void xsb_check_stream(xsb_header * xsb, int stream_index, int wavebank_index, off_t name_offset, STREAMFILE *sf) {
|
||||||
|
if (xsb->parse_done)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* multiple names may correspond to a stream, so commenting parse_done
|
||||||
|
* will allow to search for other names instead of first only */
|
||||||
|
if (xsb->selected_stream == stream_index &&
|
||||||
|
(xsb->selected_wavebank == wavebank_index || wavebank_index == -1 || wavebank_index == 255)) {
|
||||||
|
xsb->name_offset = name_offset;
|
||||||
|
xsb->parse_found = 1;
|
||||||
|
xsb->parse_done = 1;
|
||||||
|
//;VGM_LOG("XSB: parse found stream=%i, wavebank=%i, name_offset=%lx\n", stream_index, wavebank_index, name_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0 // for debugging purposes
|
||||||
|
{
|
||||||
|
char stream_name[255];
|
||||||
|
read_string(stream_name,255, name_offset,sf); /* null-terminated */
|
||||||
|
;VGM_LOG("XSB: stream=%i, wavebank=%i, name=%lx=%s vs s=%i, w=%i\n", stream_index, wavebank_index, name_offset, stream_name, xsb->selected_stream, xsb->selected_wavebank);
|
||||||
|
xsb->parse_done = 0; /* keep parsing */
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* old XACT1 is a bit different and much of it is unknown but this seems to work.
|
||||||
|
* - after header is the simple(?) cues table then complex(?) cues table
|
||||||
|
* - simple cues point to complex cues by index
|
||||||
|
* - complex cues may have stream/wavebank or point again to a sound(?) with the stream/wavebank
|
||||||
|
*/
|
||||||
|
static int parse_xsb_cues_old(xsb_header * xsb, STREAMFILE *sf) {
|
||||||
|
int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
|
||||||
|
int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
|
||||||
|
|
||||||
|
uint8_t flags, subflags;
|
||||||
|
int cue_index, stream_index, wavebank_index = 0;
|
||||||
|
off_t offset, name_offset, cue_offset, sound_offset;
|
||||||
|
int i;
|
||||||
|
size_t simple_entry, complex_entry;
|
||||||
|
|
||||||
|
|
||||||
|
if (xsb->version <= XSB_XACT1_1_MAX) {
|
||||||
|
simple_entry = 0x10;
|
||||||
|
complex_entry = 0x14;
|
||||||
|
}
|
||||||
|
else if (xsb->version <= XSB_XACT1_2_MAX) {
|
||||||
|
simple_entry = 0x14;
|
||||||
|
complex_entry = 0x14;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
VGM_LOG("XSB: unknown old format for version %x\n", xsb->version);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
offset = xsb->sounds_offset;
|
||||||
|
for (i = 0; i < xsb->simple_cues_count; i++) {
|
||||||
|
|
||||||
|
/* *** simple sound *** */
|
||||||
|
/* 00(2): flags? */
|
||||||
|
cue_index = read_s16(offset + 0x02,sf);
|
||||||
|
name_offset = read_s32(offset + 0x04,sf);
|
||||||
|
/* 06-14: unknown */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB old simple at %lx: cue=%i, name_offset=%lx\n", offset, cue_index, name_offset);
|
||||||
|
offset += simple_entry;
|
||||||
|
|
||||||
|
/* when cue_index is -1 @0x08 points to some offset (random sound type?) [ex. ATV 3 Lawless (Xbox)] */
|
||||||
|
if (cue_index < 0 && cue_index > xsb->complex_cues_count) {
|
||||||
|
VGM_LOG("XSB old: ignored cue index %i\n", cue_index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* *** complex sound *** */
|
||||||
|
cue_offset = xsb->sounds_offset + xsb->simple_cues_count*simple_entry + cue_index*complex_entry;
|
||||||
|
|
||||||
|
/* most fields looks like more flags and optional offsets depending of flags */
|
||||||
|
flags = read_u8(cue_offset + 0x0b,sf);
|
||||||
|
|
||||||
|
if (flags & 8) { /* simple */
|
||||||
|
stream_index = read_s16(cue_offset + 0x00,sf);
|
||||||
|
wavebank_index = read_s16(cue_offset + 0x02,sf);
|
||||||
|
}
|
||||||
|
//else if (flags & 4) { /* unsure */
|
||||||
|
// VGM_LOG("XSB old complex at %lx: unknown flags=%x\n", cue_offset, flags);
|
||||||
|
// continue;
|
||||||
|
//}
|
||||||
|
else { /* complex (flags none/1/2) */
|
||||||
|
sound_offset = read_s32(cue_offset + 0x00,sf);
|
||||||
|
|
||||||
|
/* *** jump entry *** */
|
||||||
|
/* 00(1): flags? */
|
||||||
|
sound_offset = read_s32(sound_offset + 0x01,sf) & 0x00FFFFFF; /* 24b */
|
||||||
|
|
||||||
|
/* *** sound entry *** */
|
||||||
|
subflags = read_u8(sound_offset + 0x00,sf);
|
||||||
|
if (subflags == 0x00) { /* 0x0c entry */
|
||||||
|
stream_index = read_s16(sound_offset + 0x08,sf);
|
||||||
|
wavebank_index = read_s16(sound_offset + 0x0a,sf);
|
||||||
|
}
|
||||||
|
else if (subflags == 0x0a) { /* 0x20 entry */
|
||||||
|
stream_index = read_s16(sound_offset + 0x1c,sf);
|
||||||
|
wavebank_index = read_s16(sound_offset + 0x1e,sf);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
VGM_LOG("XSB old sound at %lx: unknown subflags=%x\n", sound_offset, subflags);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB old complex at %lx: flags=%x, stream=%i, wavebank=%i, name_offset=%lx\n", cue_offset, flags, stream_index, wavebank_index, name_offset);
|
||||||
|
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
|
||||||
|
if (xsb->parse_done) return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
fail:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
|
||||||
|
uint32_t (*read_u32)(off_t,STREAMFILE*) = xsb->big_endian ? read_u32be : read_u32le;
|
||||||
|
int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
|
||||||
|
|
||||||
|
uint32_t flags;
|
||||||
|
int stream_index, wavebank_index;
|
||||||
|
int i, t, track_count, event_count;
|
||||||
|
|
||||||
|
|
||||||
|
event_count = read_s8(offset + 0x00,sf);
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB clip at %lx\n", offset);
|
||||||
|
offset += 0x01;
|
||||||
|
|
||||||
|
for (i = 0; i < event_count; i++) {
|
||||||
|
flags = read_u32(offset + 0x00,sf);
|
||||||
|
/* 04(2): random offset */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB clip event: %x at %lx\n", flags, offset);
|
||||||
|
offset += 0x06;
|
||||||
|
|
||||||
|
switch (flags & 0x1F) { /* event ID */
|
||||||
|
|
||||||
|
case 0x01: /* playwave event */
|
||||||
|
/* 00(1): unknown */
|
||||||
|
/* 01(1): flags */
|
||||||
|
stream_index = read_s16(offset + 0x02,sf);
|
||||||
|
wavebank_index = read_s8 (offset + 0x04,sf);
|
||||||
|
/* 05(1): loop count */
|
||||||
|
/* 06(2): pan angle */
|
||||||
|
/* 08(2): pan arc */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB clip event 1 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
|
||||||
|
offset += 0x0a;
|
||||||
|
|
||||||
|
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
|
||||||
|
if (xsb->parse_done) return 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x03: /* playwave event */
|
||||||
|
/* 00(1): unknown */
|
||||||
|
/* 01(1): flags */
|
||||||
|
/* 02(1): loop count */
|
||||||
|
/* 03(2): pan angle */
|
||||||
|
/* 05(2): pan arc */
|
||||||
|
track_count = read_s16(offset + 0x07,sf);
|
||||||
|
/* 09(1): flags? */
|
||||||
|
/* 0a(5): unknown */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB clip event 3 at %lx\n", offset);
|
||||||
|
offset += 0x0F;
|
||||||
|
|
||||||
|
for (t = 0; t < track_count; t++) {
|
||||||
|
stream_index = read_s16(offset + 0x00,sf);
|
||||||
|
wavebank_index = read_s8 (offset + 0x02,sf);
|
||||||
|
/* 03(1): min weight */
|
||||||
|
/* 04(1): min weight */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB clip event 3: track=%i, stream=%i, wavebank=%i\n", t, stream_index, wavebank_index);
|
||||||
|
offset += 0x05;
|
||||||
|
|
||||||
|
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
|
||||||
|
if (xsb->parse_done) return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x04: /* playwave event */
|
||||||
|
/* 00(1): unknown */
|
||||||
|
/* 01(1): flags */
|
||||||
|
stream_index = read_s16(offset + 0x02,sf);
|
||||||
|
wavebank_index = read_s8 (offset + 0x04,sf);
|
||||||
|
/* 05(1): loop count */
|
||||||
|
/* 06(2): pan angle */
|
||||||
|
/* 08(2): pan arc */
|
||||||
|
/* 0a(2): min pitch */
|
||||||
|
/* 0c(2): max pitch */
|
||||||
|
/* 0e(1): min volume */
|
||||||
|
/* 0f(1): max volume */
|
||||||
|
/* 10(4): min frequency */
|
||||||
|
/* 14(4): max frequency */
|
||||||
|
/* 18(1): min Q */
|
||||||
|
/* 19(1): max Q */
|
||||||
|
/* 1a(1): unknown */
|
||||||
|
/* 1b(1): variation flags */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB clip event 4 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
|
||||||
|
offset += 0x1c;
|
||||||
|
|
||||||
|
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
|
||||||
|
if (xsb->parse_done) return 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x06: /* playwave event */
|
||||||
|
/* 00(1): unknown */
|
||||||
|
/* 01(1): flags */
|
||||||
|
/* 02(1): loop count */
|
||||||
|
/* 03(2): pan angle */
|
||||||
|
/* 05(2): pan arc */
|
||||||
|
/* 07(2): min pitch */
|
||||||
|
/* 09(2): max pitch */
|
||||||
|
/* 0b(1): min volume */
|
||||||
|
/* 0c(1): max volume */
|
||||||
|
/* 0d(4): min frequency */
|
||||||
|
/* 11(4): max frequency */
|
||||||
|
/* 15(1): min Q */
|
||||||
|
/* 16(1): max Q */
|
||||||
|
/* 17(1): unknown */
|
||||||
|
/* 18(1): variation flags */
|
||||||
|
track_count = read_s16(offset + 0x19,sf);
|
||||||
|
/* 1a(1): flags 2 */
|
||||||
|
/* 1b(5): unknown 2 */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB clip event 6 at %lx\n", offset);
|
||||||
|
offset += 0x20;
|
||||||
|
|
||||||
|
for (t = 0; t < track_count; t++) {
|
||||||
|
stream_index = read_s16(offset + 0x00,sf);
|
||||||
|
wavebank_index = read_s8 (offset + 0x02,sf);
|
||||||
|
/* 03(1): min weight */
|
||||||
|
/* 04(1): min weight */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB clip event 6: track=%i, stream=%i, wavebank=%i at %lx\n", t, stream_index, wavebank_index, offset);
|
||||||
|
offset += 0x05;
|
||||||
|
|
||||||
|
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
|
||||||
|
if (xsb->parse_done) return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x08: /* volume event */
|
||||||
|
/* 00(2): unknown */
|
||||||
|
/* 02(1): flags */
|
||||||
|
/* 03(4): decibels */
|
||||||
|
/* 07(9): unknown */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB clip event 8 at %lx\n", offset);
|
||||||
|
offset += 0x10;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x00: /* stop event */
|
||||||
|
case 0x07: /* pitch event */
|
||||||
|
case 0x09: /* marker event */
|
||||||
|
case 0x11: /* volume repeat event */
|
||||||
|
default:
|
||||||
|
VGM_LOG("XSB event: unknown type %x at %lx\n", flags, offset);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
fail:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_xsb_sound(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
|
||||||
|
int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
|
||||||
|
int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
|
||||||
|
|
||||||
|
uint8_t flags;
|
||||||
|
int stream_index = 0, wavebank_index = 0;
|
||||||
|
int i, clip_count = 0;
|
||||||
|
|
||||||
|
|
||||||
|
flags = read_u8 (offset + 0x00,sf);
|
||||||
|
/* 0x01(2): category */
|
||||||
|
/* 0x03(1): decibels */
|
||||||
|
/* 0x04(2): pitch */
|
||||||
|
/* 0x06(1): priority */
|
||||||
|
/* 0x07(2): entry size? "filter stuff"? */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB sound at %lx\n", offset);
|
||||||
|
offset += 0x09;
|
||||||
|
|
||||||
|
if (flags & 0x01) { /* complex sound */
|
||||||
|
clip_count = read_u8 (offset + 0x00,sf);
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB sound: complex with clips=%i\n", clip_count);
|
||||||
|
offset += 0x01;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stream_index = read_s16(offset + 0x00,sf);
|
||||||
|
wavebank_index = read_s8(offset + 0x02,sf);
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB sound: simple with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
|
||||||
|
offset += 0x03;
|
||||||
|
|
||||||
|
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
|
||||||
|
if (xsb->parse_done) return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & 0x0E) { /* has RPCs */
|
||||||
|
size_t rpc_size = read_s16(offset + 0x00,sf);
|
||||||
|
/* 0x02(2): preset count */
|
||||||
|
/* 0x04(4*count): RPC indexes */
|
||||||
|
/* (presets per flag 2/4/8 flag) */
|
||||||
|
offset += rpc_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & 0x10) { /* has DSPs */
|
||||||
|
size_t dsp_size = read_s16(offset + 0x00,sf);
|
||||||
|
/* follows RPC format? */
|
||||||
|
offset += dsp_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & 0x01) { /* complex sound clips */
|
||||||
|
off_t clip_offset;
|
||||||
|
for (i = 0; i < clip_count; i++) {
|
||||||
|
/* 00(1): decibels */
|
||||||
|
clip_offset = read_s32(offset + 0x01,sf);
|
||||||
|
/* 05(2): filter config */
|
||||||
|
/* 07(2): filter frequency */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB sound clip %i at %lx\n", i, offset);
|
||||||
|
offset += 0x09;
|
||||||
|
|
||||||
|
parse_xsb_clip(xsb, clip_offset, name_offset,sf);
|
||||||
|
if (xsb->parse_done) return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_xsb_variation(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
|
||||||
|
int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
|
||||||
|
uint16_t (*read_u16)(off_t,STREAMFILE*) = xsb->big_endian ? read_u16be : read_u16le;
|
||||||
|
int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
|
||||||
|
|
||||||
|
uint16_t flags;
|
||||||
|
int stream_index, wavebank_index;
|
||||||
|
int i, variation_count;
|
||||||
|
|
||||||
|
|
||||||
|
variation_count = read_s16(offset + 0x00,sf);
|
||||||
|
flags = read_u16(offset + 0x02,sf);
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB variation at %lx\n", offset);
|
||||||
|
offset += 0x04;
|
||||||
|
|
||||||
|
for (i = 0; i < variation_count; i++) {
|
||||||
|
off_t sound_offset;
|
||||||
|
|
||||||
|
switch ((flags >> 3) & 0x7) {
|
||||||
|
case 0: /* wave */
|
||||||
|
stream_index = read_s16(offset + 0x00,sf);
|
||||||
|
wavebank_index = read_s8(offset + 0x02,sf);
|
||||||
|
/* 03(1): weight min */
|
||||||
|
/* 04(1): weight max */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB variation: type 0 with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
|
||||||
|
offset += 0x05;
|
||||||
|
|
||||||
|
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
|
||||||
|
if (xsb->parse_done) return 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: /* sound */
|
||||||
|
sound_offset = read_s32(offset + 0x00,sf);
|
||||||
|
/* 04(1): weight min */
|
||||||
|
/* 05(1): weight max */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB variation: type 1\n");
|
||||||
|
offset += 0x06;
|
||||||
|
|
||||||
|
parse_xsb_sound(xsb, sound_offset, name_offset,sf);
|
||||||
|
if (xsb->parse_done) return 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: /* sound */
|
||||||
|
sound_offset = read_s32(offset + 0x00,sf);
|
||||||
|
/* 04(4): weight min */
|
||||||
|
/* 08(4): weight max */
|
||||||
|
/* 0c(4): flags */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB variation: type 3\n");
|
||||||
|
offset += 0x10;
|
||||||
|
|
||||||
|
parse_xsb_sound(xsb, sound_offset, name_offset,sf);
|
||||||
|
if (xsb->parse_done) return 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4: /* compact wave */
|
||||||
|
stream_index = read_s16(offset + 0x00,sf);
|
||||||
|
wavebank_index = read_s8(offset + 0x02,sf);
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB variation: type 4 with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
|
||||||
|
offset += 0x03;
|
||||||
|
|
||||||
|
xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
|
||||||
|
if (xsb->parse_done) return 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
VGM_LOG("XSB variation: unknown type %x at %lx\n", flags, offset);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 00(1): unknown */
|
||||||
|
/* 01(2): unknown */
|
||||||
|
/* 03(1): unknown */
|
||||||
|
offset += 0x04;
|
||||||
|
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
fail:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int parse_xsb_cues_new(xsb_header * xsb, STREAMFILE *sf) {
|
||||||
|
int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
|
||||||
|
|
||||||
|
uint8_t flags;
|
||||||
|
off_t offset, name_offset, sound_offset;
|
||||||
|
off_t names_offset = xsb->nameoffsets_offset;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
|
||||||
|
offset = xsb->simple_cues_offset;
|
||||||
|
for (i = 0; i < xsb->simple_cues_count; i++) {
|
||||||
|
/* 00(1): flags */
|
||||||
|
sound_offset = read_s32(offset + 0x01,sf);
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB cues: simple %i at %lx\n", i, offset);
|
||||||
|
offset += 0x05;
|
||||||
|
|
||||||
|
name_offset = read_s32(names_offset + 0x00,sf);
|
||||||
|
/* 04(2): unknown (-1) */
|
||||||
|
names_offset += 0x06;
|
||||||
|
|
||||||
|
parse_xsb_sound(xsb, sound_offset, name_offset,sf);
|
||||||
|
if (xsb->parse_done) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = xsb->complex_cues_offset;
|
||||||
|
for (i = 0; i < xsb->complex_cues_count; i++) {
|
||||||
|
flags = read_u8(offset + 0x00,sf);
|
||||||
|
sound_offset = read_s32(offset + 0x01,sf);
|
||||||
|
/* 05(4): unknown (sound) / transition table offset (variation) */
|
||||||
|
/* 09(1): instance limit */
|
||||||
|
/* 0a(2): fade in sec */
|
||||||
|
/* 0c(2): fade out sec */
|
||||||
|
/* 0e(1): instance flags */
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB cues: complex %i at %lx\n", i, offset);
|
||||||
|
offset += 0x0f;
|
||||||
|
|
||||||
|
name_offset = read_s32(names_offset + 0x00,sf);
|
||||||
|
/* 04(2): unknown (-1) */
|
||||||
|
names_offset += 0x06;
|
||||||
|
|
||||||
|
if (flags & (1<<2))
|
||||||
|
parse_xsb_sound(xsb, sound_offset, name_offset,sf);
|
||||||
|
else
|
||||||
|
parse_xsb_variation(xsb, sound_offset, name_offset,sf);
|
||||||
|
if (xsb->parse_done) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XWB "wave bank" has streams (channels, loops, etc), while XSB "sound bank" has cues/sounds
|
||||||
|
* (volume, pitch, name, etc). Each XSB cue/sound has a variable size and somewhere inside may
|
||||||
|
* be the stream/wavebank index (some cues are just commands, though).
|
||||||
|
*
|
||||||
|
* We want to find a cue pointing to our current wave to get the name. Cues may point to
|
||||||
|
* multiple streams out of order, and a stream can be used by multiple cues:
|
||||||
|
* - name 1: simple cue 1 > simple sound 2 > xwb stream 3
|
||||||
|
* - name 2: simple cue 2 > complex sound 1 > clip 1/2/3 > xwb streams 4/5/5
|
||||||
|
* - name 3: complex cue 1 > simple sound 3 > xwb stream 0
|
||||||
|
* - name 4: complex cue 2 > variation > xwb stream 1
|
||||||
|
* - name 5: complex cue 3 > variation > simple sound 4/5 > xwb streams 0/1
|
||||||
|
* - etc
|
||||||
|
* Names are optional (almost always included though), and some cues don't have a name
|
||||||
|
* even if others do. Some offsets are optional, usually signaled by -1/wrong values.
|
||||||
|
*
|
||||||
|
* More info:
|
||||||
|
* - https://wiki.multimedia.cx/index.php/XACT
|
||||||
|
* - https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Audio/Xact/
|
||||||
|
* - https://github.com/espes/MacTerrariaWrapper/tree/master/xactxtract
|
||||||
|
*/
|
||||||
|
static int parse_xsb(xsb_header * xsb, STREAMFILE *sf, char *xwb_wavebank_name) {
|
||||||
|
int32_t (*read_s32)(off_t,STREAMFILE*) = NULL;
|
||||||
|
int16_t (*read_s16)(off_t,STREAMFILE*) = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
/* check header */
|
||||||
|
if ((read_u32be(0x00,sf) != 0x5344424B) && /* "SDBK" (LE) */
|
||||||
|
(read_u32be(0x00,sf) != 0x4B424453)) /* "KBDS" (BE) */
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
xsb->big_endian = (read_u32be(0x00,sf) == 0x4B424453); /* "KBDS" */
|
||||||
|
read_s32 = xsb->big_endian ? read_s32be : read_s32le;
|
||||||
|
read_s16 = xsb->big_endian ? read_s16be : read_s16le;
|
||||||
|
|
||||||
|
|
||||||
|
/* parse sound bank header */
|
||||||
|
xsb->version = read_s16(0x04,sf); /* tool version */
|
||||||
|
if (xsb->version <= XSB_XACT1_0_MAX) {
|
||||||
|
/* 06(2): crc */
|
||||||
|
xsb->wavebanks_offset = read_s32(0x08,sf);
|
||||||
|
/* 0c(4): unknown1 offset (entry: 0x04) */
|
||||||
|
/* 10(4): unknown2 offset */
|
||||||
|
/* 14(2): element count? */
|
||||||
|
/* 16(2): empty? */
|
||||||
|
/* 18(2): empty? */
|
||||||
|
xsb->complex_cues_count = read_s16(0x1a,sf);
|
||||||
|
xsb->simple_cues_count = read_s16(0x1c,sf);
|
||||||
|
xsb->wavebanks_count = read_s16(0x1e,sf);
|
||||||
|
/* 20(10): xsb name */
|
||||||
|
|
||||||
|
xsb->sounds_offset = 0x30;
|
||||||
|
xsb->wavebanks_name_size = 0x10;
|
||||||
|
}
|
||||||
|
else if (xsb->version <= XSB_XACT1_1_MAX) {
|
||||||
|
/* 06(2): crc */
|
||||||
|
xsb->wavebanks_offset = read_s32(0x08,sf);
|
||||||
|
/* 0c(4): unknown1 offset (entry: 0x04) */
|
||||||
|
/* 10(4): unknown2 offset */
|
||||||
|
/* 14(4): unknown3 offset */
|
||||||
|
/* 18(2): empty? */
|
||||||
|
/* 1a(2): element count? */
|
||||||
|
xsb->complex_cues_count = read_s16(0x1c,sf);
|
||||||
|
xsb->simple_cues_count = read_s16(0x1e,sf);
|
||||||
|
/* 20(2): unknown count? (related to unknown2?) */
|
||||||
|
xsb->wavebanks_count = read_s16(0x22,sf);
|
||||||
|
/* 24(10): xsb name */
|
||||||
|
|
||||||
|
xsb->sounds_offset = 0x34;
|
||||||
|
xsb->wavebanks_name_size = 0x10;
|
||||||
|
}
|
||||||
|
else if (xsb->version <= XSB_XACT1_2_MAX) {
|
||||||
|
/* 06(2): crc */
|
||||||
|
xsb->wavebanks_offset = read_s32(0x08,sf);
|
||||||
|
/* 0c(4): unknown1 offset (entry: 0x14) */
|
||||||
|
/* 10(4): unknown2 offset (entry: variable) */
|
||||||
|
/* 14(4): unknown3 offset */
|
||||||
|
/* 18(2): empty? */
|
||||||
|
/* 1a(2): element count? */
|
||||||
|
xsb->complex_cues_count = read_s16(0x1c,sf);
|
||||||
|
xsb->simple_cues_count = read_s16(0x1e,sf);
|
||||||
|
/* 20(2): unknown count? (related to unknown2?) */
|
||||||
|
xsb->wavebanks_count = read_s16(0x22,sf);
|
||||||
|
/* 24(4): null? */
|
||||||
|
/* 28(10): xsb name */
|
||||||
|
|
||||||
|
xsb->sounds_offset = 0x38;
|
||||||
|
xsb->wavebanks_name_size = 0x10;
|
||||||
|
}
|
||||||
|
else if (xsb->version <= XSB_XACT2_MAX) {
|
||||||
|
/* 06(2): crc */
|
||||||
|
/* 08(1): platform? (3=X360) */
|
||||||
|
xsb->simple_cues_count = read_s16(0x09,sf);
|
||||||
|
xsb->complex_cues_count = read_s16(0x0B,sf);
|
||||||
|
xsb->wavebanks_count = read_s8 (0x11,sf);
|
||||||
|
xsb->sounds_count = read_s16(0x12,sf);
|
||||||
|
/* 14(2): unknown */
|
||||||
|
xsb->cue_names_size = read_s32(0x16,sf);
|
||||||
|
xsb->simple_cues_offset = read_s32(0x1a,sf);
|
||||||
|
xsb->complex_cues_offset = read_s32(0x1e,sf);
|
||||||
|
xsb->cue_names_offset = read_s32(0x22,sf);
|
||||||
|
/* 26(4): unknown */
|
||||||
|
/* 2a(4): unknown */
|
||||||
|
/* 2e(4): unknown */
|
||||||
|
xsb->wavebanks_offset = read_s32(0x32,sf);
|
||||||
|
/* 36(4): cue name hash table offset? */
|
||||||
|
xsb->nameoffsets_offset = read_s32(0x3a,sf);
|
||||||
|
xsb->sounds_offset = read_s32(0x3e,sf);
|
||||||
|
/* 42(4): unknown */
|
||||||
|
/* 46(4): unknown */
|
||||||
|
/* 4a(64): xsb name */
|
||||||
|
|
||||||
|
xsb->wavebanks_name_size = 0x40;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* 06(2): format version */
|
||||||
|
/* 08(2): crc (fcs16 checksum of all following data) */
|
||||||
|
/* 0a(4): last modified low */
|
||||||
|
/* 0e(4): last modified high */
|
||||||
|
/* 12(1): platform? (1=PC, 3=X360) */
|
||||||
|
xsb->simple_cues_count = read_s16(0x13,sf);
|
||||||
|
xsb->complex_cues_count = read_s16(0x15,sf);
|
||||||
|
/* 17(2): unknown count? */
|
||||||
|
/* 19(2): element count? (often simple+complex cues, but may be more) */
|
||||||
|
xsb->wavebanks_count = read_s8 (0x1b,sf);
|
||||||
|
xsb->sounds_count = read_s16(0x1c,sf);
|
||||||
|
xsb->cue_names_size = read_s32(0x1e,sf);
|
||||||
|
xsb->simple_cues_offset = read_s32(0x22,sf);
|
||||||
|
xsb->complex_cues_offset = read_s32(0x26,sf);
|
||||||
|
xsb->cue_names_offset = read_s32(0x2a,sf);
|
||||||
|
/* 0x2E(4): unknown offset */
|
||||||
|
/* 0x32(4): variation tables offset */
|
||||||
|
/* 0x36(4): unknown offset */
|
||||||
|
xsb->wavebanks_offset = read_s32(0x3a,sf);
|
||||||
|
/* 0x3E(4): cue name hash table offset (16b each) */
|
||||||
|
xsb->nameoffsets_offset = read_s32(0x42,sf);
|
||||||
|
xsb->sounds_offset = read_s32(0x46,sf);
|
||||||
|
/* 4a(64): xsb name */
|
||||||
|
|
||||||
|
xsb->wavebanks_name_size = 0x40;
|
||||||
|
}
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB header: version=%i\n", xsb->version);
|
||||||
|
//;VGM_LOG("XSB header: count: simple=%i, complex=%i, wavebanks=%i, sounds=%i\n",
|
||||||
|
// xsb->simple_cues_count, xsb->complex_cues_count, xsb->wavebanks_count, xsb->sounds_count);
|
||||||
|
//;VGM_LOG("XSB header: offset: simple=%lx, complex=%lx, wavebanks=%lx, sounds=%lx\n",
|
||||||
|
// xsb->simple_cues_offset, xsb->complex_cues_offset, xsb->wavebanks_offset, xsb->sounds_offset);
|
||||||
|
//;VGM_LOG("XSB header: names: cues=%lx, size=%x, hash=%lx\n",
|
||||||
|
// xsb->cue_names_offset, xsb->cue_names_size, xsb->nameoffsets_offset);
|
||||||
|
|
||||||
|
if (xsb->version > XSB_XACT1_2_MAX && xsb->cue_names_size <= 0) {
|
||||||
|
VGM_LOG("XSB: no names found\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* find target wavebank */
|
||||||
|
if (xsb->wavebanks_count) {
|
||||||
|
char xsb_wavebank_name[64+1];
|
||||||
|
int i;
|
||||||
|
off_t offset;
|
||||||
|
|
||||||
|
xsb->selected_wavebank = -1;
|
||||||
|
|
||||||
|
offset = xsb->wavebanks_offset;
|
||||||
|
for (i = 0; i < xsb->wavebanks_count; i++) {
|
||||||
|
read_string(xsb_wavebank_name,xsb->wavebanks_name_size, offset,sf);
|
||||||
|
//;VGM_LOG("XSB wavebanks: bank %i=%s\n", i, wavebank_name);
|
||||||
|
if (strcasecmp(xsb_wavebank_name, xwb_wavebank_name)==0) {
|
||||||
|
//;VGM_LOG("XSB banks: current xwb is wavebank %i=%s\n", i, xsb_wavebank_name);
|
||||||
|
xsb->selected_wavebank = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += xsb->wavebanks_name_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
//;VGM_LOG("xsb: selected wavebank=%i\n", xsb->selected_wavebank);
|
||||||
|
if (xsb->selected_wavebank == -1) {
|
||||||
|
VGM_LOG("XSB: current wavebank not found, selecting first\n");
|
||||||
|
xsb->selected_wavebank = 0; //todo goto fail?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* find cue pointing to stream */
|
||||||
|
if (xsb->version <= XSB_XACT1_2_MAX) {
|
||||||
|
parse_xsb_cues_old(xsb, sf);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parse_xsb_cues_new(xsb, sf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
fail:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static STREAMFILE * open_xsb_filename_pair(STREAMFILE *streamXwb) {
|
||||||
|
STREAMFILE *streamXsb = NULL;
|
||||||
|
/* .xwb to .xsb name conversion, since often they don't match */
|
||||||
|
static const char *const filename_pairs[][2] = {
|
||||||
|
{"MUSIC.xwb","Everything.xsb"}, /* Unreal Championship (Xbox) */
|
||||||
|
{"Music.xwb","Sound Bank.xsb"}, /* Stardew Valley (Vita) */
|
||||||
|
{"Ambiences_intro.xwb","Ambiences.xsb"}, /* Arx Fatalis (Xbox) */
|
||||||
|
{"Wave*.xwb","Sound*.xsb"}, /* XNA/MonoGame games? */
|
||||||
|
{"*MusicBank.xwb","*SoundBank.xsb"}, /* NFL Fever 2004 (Xbox) */
|
||||||
|
{"*_xwb","*_xsb"}, /* Ikaruga (PC) */
|
||||||
|
{"WB_*","SB_*"}, /* Ikaruga (X360) */
|
||||||
|
{"*StreamBank.xwb","*SoundBank.xsb"}, /* Eschatos (X360) */
|
||||||
|
{"*WaveBank.xwb","*SoundBank.xsb"}, /* Eschatos (X360) */
|
||||||
|
{"StreamBank_*.xwb","SoundBank_*.xsb"}, /* Ginga Force (X360) */
|
||||||
|
{"WaveBank_*.xwb","SoundBank_*.xsb"}, /* Ginga Force (X360) */
|
||||||
|
{"*_WB.xwb","*_SB.xsb"}, /* Ninja Blade (X360) */
|
||||||
|
{"*.xwb","*.xsb"}, /* default */
|
||||||
|
};
|
||||||
|
int i;
|
||||||
|
int pair_count = (sizeof(filename_pairs) / sizeof(filename_pairs[0]));
|
||||||
|
char target_filename[PATH_LIMIT];
|
||||||
|
char temp_filename[PATH_LIMIT];
|
||||||
|
int target_len;
|
||||||
|
|
||||||
|
/* try names in external .xsb, using a bunch of possible name pairs */
|
||||||
|
get_streamfile_filename(streamXwb,target_filename,PATH_LIMIT);
|
||||||
|
target_len = strlen(target_filename);
|
||||||
|
|
||||||
|
for (i = 0; i < pair_count; i++) {
|
||||||
|
const char *xwb_match = filename_pairs[i][0];
|
||||||
|
const char *xsb_match = filename_pairs[i][1];
|
||||||
|
size_t xwb_len = strlen(xwb_match);
|
||||||
|
size_t xsb_len = strlen(xsb_match);
|
||||||
|
int match_pos1 = -1, match_pos2 = -1, xwb_pos = -1 , xsb_pos = -1, new_len = 0;
|
||||||
|
const char * teststr;
|
||||||
|
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB: pair1 '%s'='%s' << '%s' \n", xwb_match, xsb_match, target_filename);
|
||||||
|
if (target_len < xwb_len)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* ghetto string wildcard replace, ex:
|
||||||
|
* - target filename = "start1_wildcard_end1", xwb_match = "start1_*_end1", xsb_match = "start2_*_end2"
|
||||||
|
* > check xwb's "start_" starts in target_filename (from 0..xwb_pos), set match_pos1
|
||||||
|
* > check xwb's "_end" ends in target_filename (from xwb_pos+1..end), set match_pos2
|
||||||
|
* > copy xsb's "start2_" (from 0..xsb_pos)
|
||||||
|
* > copy target "wildcard" (from 0..xsb_pos)
|
||||||
|
* > copy xsb's "end" (from xsb_pos+1..end)
|
||||||
|
* > final target_filename is "start2_wildcard_end2"
|
||||||
|
* (skips start/end if wildcard is at start/end)
|
||||||
|
*/
|
||||||
|
|
||||||
|
teststr = strchr(xwb_match, '*');
|
||||||
|
if (teststr)
|
||||||
|
xwb_pos = (intptr_t)teststr - (intptr_t)xwb_match;
|
||||||
|
teststr = strchr(xsb_match, '*');
|
||||||
|
if (teststr)
|
||||||
|
xsb_pos = (intptr_t)teststr - (intptr_t)xsb_match;
|
||||||
|
|
||||||
|
match_pos1 = 0;
|
||||||
|
match_pos2 = target_len;
|
||||||
|
temp_filename[0] = '\0';
|
||||||
|
|
||||||
|
if (xwb_pos < 0) { /* no wildcard, check exact match */
|
||||||
|
if (target_len != xwb_len || strncmp(target_filename, xwb_match, xwb_len))
|
||||||
|
continue;
|
||||||
|
strcpy(target_filename, xsb_match);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xwb_pos > 0) { /* wildcard after start, check starts_with */
|
||||||
|
int starts_len = xwb_pos;
|
||||||
|
if (strncmp(target_filename + 0, xwb_match + 0, xwb_pos) != 0)
|
||||||
|
continue;
|
||||||
|
match_pos1 = 0 + starts_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xwb_pos >= 0 && xwb_pos + 1 < xwb_len) { /* wildcard before end, check ends_with */
|
||||||
|
int ends_len = xwb_len - (xwb_pos+1);
|
||||||
|
if (strncmp(target_filename + target_len - ends_len, xwb_match + xwb_len - ends_len, ends_len) != 0)
|
||||||
|
continue;
|
||||||
|
match_pos2 = target_len - ends_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match_pos1 >= 0 && match_pos2 > match_pos1) { /* save match */
|
||||||
|
int match_len = match_pos2 - match_pos1;
|
||||||
|
strncpy(temp_filename, target_filename + match_pos1, match_len);
|
||||||
|
temp_filename[match_len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xsb_pos > 0) { /* copy xsb start */
|
||||||
|
strncpy(target_filename + 0, xsb_match, (xsb_pos));
|
||||||
|
new_len += (xsb_pos);
|
||||||
|
target_filename[new_len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xsb_pos >= 0){ /* copy xsb match */
|
||||||
|
strncpy(target_filename + new_len, temp_filename, (match_pos2 - match_pos1));
|
||||||
|
new_len += (match_pos2 - match_pos1);
|
||||||
|
target_filename[new_len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xsb_pos >= 0 && xsb_pos + 1 < xsb_len) { /* copy xsb end */
|
||||||
|
strncpy(target_filename + new_len, xsb_match + (xsb_pos+1), (xsb_len - (xsb_pos+1)));
|
||||||
|
new_len += (xsb_len - (xsb_pos+1));
|
||||||
|
target_filename[new_len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
//;VGM_LOG("XSB: pair2 '%s'='%s' >> '%s'\n", xwb_match, xsb_match, target_filename);
|
||||||
|
streamXsb = open_streamfile_by_filename(streamXwb, target_filename);
|
||||||
|
if (streamXsb) return streamXsb;
|
||||||
|
|
||||||
|
get_streamfile_filename(streamXwb,target_filename,PATH_LIMIT); /* reset for next loop */
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _XWB_XSB_H_ */
|
@ -194,10 +194,10 @@ static inline int8_t read_8bit(off_t offset, STREAMFILE * streamfile) {
|
|||||||
/* alias of the above */
|
/* alias of the above */
|
||||||
static inline int8_t read_s8(off_t offset, STREAMFILE * streamfile) { return read_8bit(offset, streamfile); }
|
static inline int8_t read_s8(off_t offset, STREAMFILE * streamfile) { return read_8bit(offset, streamfile); }
|
||||||
static inline uint8_t read_u8(off_t offset, STREAMFILE * streamfile) { return (uint8_t)read_8bit(offset, streamfile); }
|
static inline uint8_t read_u8(off_t offset, STREAMFILE * streamfile) { return (uint8_t)read_8bit(offset, streamfile); }
|
||||||
static inline int32_t read_s16le(off_t offset, STREAMFILE * streamfile) { return read_16bitLE(offset, streamfile); }
|
static inline int16_t read_s16le(off_t offset, STREAMFILE * streamfile) { return read_16bitLE(offset, streamfile); }
|
||||||
static inline uint32_t read_u16le(off_t offset, STREAMFILE * streamfile) { return (uint16_t)read_16bitLE(offset, streamfile); }
|
static inline uint16_t read_u16le(off_t offset, STREAMFILE * streamfile) { return (uint16_t)read_16bitLE(offset, streamfile); }
|
||||||
static inline int32_t read_s16be(off_t offset, STREAMFILE * streamfile) { return read_16bitBE(offset, streamfile); }
|
static inline int16_t read_s16be(off_t offset, STREAMFILE * streamfile) { return read_16bitBE(offset, streamfile); }
|
||||||
static inline uint32_t read_u16be(off_t offset, STREAMFILE * streamfile) { return (uint16_t)read_16bitBE(offset, streamfile); }
|
static inline uint16_t read_u16be(off_t offset, STREAMFILE * streamfile) { return (uint16_t)read_16bitBE(offset, streamfile); }
|
||||||
static inline int32_t read_s32le(off_t offset, STREAMFILE * streamfile) { return read_32bitLE(offset, streamfile); }
|
static inline int32_t read_s32le(off_t offset, STREAMFILE * streamfile) { return read_32bitLE(offset, streamfile); }
|
||||||
static inline uint32_t read_u32le(off_t offset, STREAMFILE * streamfile) { return (uint32_t)read_32bitLE(offset, streamfile); }
|
static inline uint32_t read_u32le(off_t offset, STREAMFILE * streamfile) { return (uint32_t)read_32bitLE(offset, streamfile); }
|
||||||
static inline int32_t read_s32be(off_t offset, STREAMFILE * streamfile) { return read_32bitBE(offset, streamfile); }
|
static inline int32_t read_s32be(off_t offset, STREAMFILE * streamfile) { return read_32bitBE(offset, streamfile); }
|
||||||
|
Loading…
Reference in New Issue
Block a user