2008-12-18 20:04:35 +01:00
# include "meta.h"
# include "../util.h"
2017-02-25 13:57:18 +01:00
# include "../coding/coding.h"
2010-01-10 18:29:19 +01:00
2017-02-25 13:57:18 +01:00
/* most info from XWBtool, xactwb.h, xact2wb.h and xact3wb.h */
2010-01-10 18:29:19 +01:00
2017-02-25 13:57:18 +01:00
# define WAVEBANK_FLAGS_COMPACT 0x00020000 // Bank uses compact format
2017-04-22 09:53:28 +02:00
# define WAVEBANKENTRY_FLAGS_IGNORELOOP 0x00000008 // Used internally when the loop region can't be used (no idea...)
/* the x.x version is just to make it clearer, MS only classifies XACT as 1/2/3 */
# define XACT1_0_MAX 1 /* Project Gotham Racing 2 (v1), Silent Hill 4 (v1) */
2018-01-27 10:57:29 +01:00
# define XACT1_1_MAX 3 /* Unreal Championship (v2), The King of Fighters 2003 (v3) */
2017-04-22 09:53:28 +02:00
# define XACT2_0_MAX 34 /* Dead or Alive 4 (v17), Kameo (v23), Table Tennis (v34) */ // v35/36/37 too?
# define XACT2_1_MAX 38 /* Prey (v38) */ // v39 too?
# define XACT2_2_MAX 41 /* Blue Dragon (v40) */
# define XACT3_0_MAX 46 /* Ninja Blade (t43 v42), Persona 4 Ultimax NESSICA (t45 v43) */
2017-05-18 22:14:32 +02:00
# define XACT_TECHLAND 0x10000 /* Sniper Ghost Warrior, Nail'd (PS3/X360), equivalent to XACT3_0 */
# define XACT_CRACKDOWN 0x87 /* Crackdown 1, equivalent to XACT2_2 */
2017-04-22 09:53:28 +02:00
static const int wma_avg_bps_index [ 7 ] = {
2017-02-25 13:57:18 +01:00
12000 , 24000 , 4000 , 6000 , 8000 , 20000 , 2500
} ;
2017-04-22 09:53:28 +02:00
static const int wma_block_align_index [ 17 ] = {
2017-02-25 13:57:18 +01:00
929 , 1487 , 1280 , 2230 , 8917 , 8192 , 4459 , 5945 , 2304 , 1536 , 1485 , 1008 , 2731 , 4096 , 6827 , 5462 , 1280
} ;
2017-11-10 19:31:54 +01:00
typedef enum { PCM , XBOX_ADPCM , MS_ADPCM , XMA1 , XMA2 , WMA , XWMA , ATRAC3 , OGG } xact_codec ;
2017-02-25 13:57:18 +01:00
typedef struct {
int little_endian ;
int version ;
/* segments */
off_t base_offset ;
size_t base_size ;
off_t entry_offset ;
size_t entry_size ;
off_t data_offset ;
size_t data_size ;
off_t stream_offset ;
size_t stream_size ;
uint32_t base_flags ;
size_t entry_elem_size ;
size_t entry_alignment ;
int streams ;
uint32_t entry_flags ;
uint32_t format ;
int tag ;
int channels ;
int sample_rate ;
int block_align ;
int bits_per_sample ;
xact_codec codec ;
int loop_flag ;
uint32_t num_samples ;
uint32_t loop_start ;
uint32_t loop_end ;
uint32_t loop_start_sample ;
uint32_t loop_end_sample ;
} xwb_header ;
2017-08-12 11:46:28 +02:00
static void get_xsb_name ( char * buf , size_t maxsize , int target_stream , xwb_header * xwb , STREAMFILE * streamFile ) ;
2017-02-25 13:57:18 +01:00
/* XWB - XACT Wave Bank (Microsoft SDK format for XBOX/XBOX360/Windows) */
2008-12-18 20:04:35 +01:00
VGMSTREAM * init_vgmstream_xwb ( STREAMFILE * streamFile ) {
VGMSTREAM * vgmstream = NULL ;
2017-02-25 13:57:18 +01:00
off_t start_offset , off , suboff ;
xwb_header xwb ;
2017-08-12 11:46:28 +02:00
int target_stream = streamFile - > stream_index ;
2017-02-25 13:57:18 +01:00
int32_t ( * read_32bit ) ( off_t , STREAMFILE * ) = NULL ;
/* basic checks */
if ( ! check_extensions ( streamFile , " xwb " ) ) goto fail ;
if ( ( read_32bitBE ( 0x00 , streamFile ) ! = 0x57424E44 ) & & /* "WBND" (LE) */
( read_32bitBE ( 0x00 , streamFile ) ! = 0x444E4257 ) ) /* "DNBW" (BE) */
2010-01-10 18:29:19 +01:00
goto fail ;
2008-12-18 20:04:35 +01:00
2017-02-25 13:57:18 +01:00
memset ( & xwb , 0 , sizeof ( xwb_header ) ) ;
2008-12-18 20:04:35 +01:00
2017-02-25 13:57:18 +01:00
xwb . little_endian = read_32bitBE ( 0x00 , streamFile ) = = 0x57424E44 ; /* WBND */
if ( xwb . little_endian ) {
read_32bit = read_32bitLE ;
} else {
read_32bit = read_32bitBE ;
}
2008-12-18 20:04:35 +01:00
2017-02-25 13:57:18 +01:00
/* read main header (WAVEBANKHEADER) */
2017-04-22 09:53:28 +02:00
xwb . version = read_32bit ( 0x04 , streamFile ) ; /* XACT3: 0x04=tool version, 0x08=header version */
2008-12-18 20:04:35 +01:00
2017-05-18 22:14:32 +02:00
/* Crackdown 1 X360, essentially XACT2 but may have split header in some cases */
if ( xwb . version = = XACT_CRACKDOWN )
xwb . version = XACT2_2_MAX ;
2017-02-25 13:57:18 +01:00
/* read segment offsets (SEGIDX) */
2017-04-22 09:53:28 +02:00
if ( xwb . version < = XACT1_0_MAX ) {
xwb . streams = read_32bit ( 0x0c , streamFile ) ;
/* 0x10: bank name */
xwb . entry_elem_size = 0x14 ;
xwb . entry_offset = 0x50 ;
xwb . entry_size = xwb . entry_elem_size * xwb . streams ;
xwb . data_offset = xwb . entry_offset + xwb . entry_size ;
xwb . data_size = get_streamfile_size ( streamFile ) - xwb . data_offset ;
}
else {
off = xwb . version < = XACT2_2_MAX ? 0x08 : 0x0c ;
xwb . base_offset = read_32bit ( off + 0x00 , streamFile ) ; //BANKDATA
xwb . base_size = read_32bit ( off + 0x04 , streamFile ) ;
xwb . entry_offset = read_32bit ( off + 0x08 , streamFile ) ; //ENTRYMETADATA
xwb . entry_size = read_32bit ( off + 0x0c , streamFile ) ;
/* go to last segment (XACT2/3 have 5 segments, XACT1 4) */
//0x10: XACT1/2: ENTRYNAMES, XACT3: SEEKTABLES
//0x14: XACT1: none (ENTRYWAVEDATA), XACT2: EXTRA, XACT3: ENTRYNAMES
suboff = xwb . version < = XACT1_1_MAX ? 0x08 : 0x08 + 0x08 ;
xwb . data_offset = read_32bit ( off + 0x10 + suboff , streamFile ) ; //ENTRYWAVEDATA
xwb . data_size = read_32bit ( off + 0x14 + suboff , streamFile ) ;
/* for Techland's XWB with no data */
if ( xwb . base_offset = = 0 ) goto fail ;
/* read base entry (WAVEBANKDATA) */
off = xwb . base_offset ;
xwb . base_flags = ( uint32_t ) read_32bit ( off + 0x00 , streamFile ) ;
xwb . streams = read_32bit ( off + 0x04 , streamFile ) ;
/* 0x08 bank_name */
suboff = 0x08 + ( xwb . version < = XACT1_1_MAX ? 0x10 : 0x40 ) ;
xwb . entry_elem_size = read_32bit ( off + suboff + 0x00 , streamFile ) ;
/* suboff+0x04: meta name entry size */
xwb . entry_alignment = read_32bit ( off + suboff + 0x08 , streamFile ) ; /* usually 1 dvd sector */
xwb . format = read_32bit ( off + suboff + 0x0c , streamFile ) ; /* compact mode only */
/* suboff+0x10: build time 64b (XACT2/3) */
}
2017-02-25 13:57:18 +01:00
if ( target_stream = = 0 ) target_stream = 1 ; /* auto: default to 1 */
2017-03-18 17:17:24 +01:00
if ( target_stream < 0 | | target_stream > xwb . streams | | xwb . streams < 1 ) goto fail ;
2009-03-12 16:28:59 +01:00
2017-02-25 13:57:18 +01:00
/* read stream entry (WAVEBANKENTRY) */
off = xwb . entry_offset + ( target_stream - 1 ) * xwb . entry_elem_size ;
2009-03-12 16:28:59 +01:00
2017-02-25 13:57:18 +01:00
if ( xwb . base_flags & WAVEBANK_FLAGS_COMPACT ) { /* compact entry */
/* offset_in_sectors:21 and sector_alignment_in_bytes:11 */
2017-02-25 19:53:21 +01:00
uint32_t entry = ( uint32_t ) read_32bit ( off + 0x00 , streamFile ) ;
xwb . stream_offset = xwb . data_offset + ( entry > > 11 ) * xwb . entry_alignment + ( entry & 0x7FF ) ;
2017-02-25 13:57:18 +01:00
/* find size (up to next entry or data end) */
if ( xwb . streams > 1 ) {
entry = ( uint32_t ) read_32bit ( off + xwb . entry_size , streamFile ) ;
xwb . stream_size = xwb . stream_offset -
( xwb . data_offset + ( entry > > 11 ) * xwb . entry_alignment + ( entry & 0x7FF ) ) ;
} else {
xwb . stream_size = xwb . data_size ;
}
}
2017-04-22 09:53:28 +02:00
else if ( xwb . version < = XACT1_0_MAX ) {
xwb . format = ( uint32_t ) read_32bit ( off + 0x00 , streamFile ) ;
xwb . stream_offset = xwb . data_offset + ( uint32_t ) read_32bit ( off + 0x04 , streamFile ) ;
xwb . stream_size = ( uint32_t ) read_32bit ( off + 0x08 , streamFile ) ;
xwb . loop_start = ( uint32_t ) read_32bit ( off + 0x0c , streamFile ) ;
xwb . loop_end = ( uint32_t ) read_32bit ( off + 0x10 , streamFile ) ; //length
}
2017-02-25 13:57:18 +01:00
else {
uint32_t entry_info = ( uint32_t ) read_32bit ( off + 0x00 , streamFile ) ;
2017-04-22 09:53:28 +02:00
if ( xwb . version < = XACT1_1_MAX ) {
2017-02-25 13:57:18 +01:00
xwb . entry_flags = entry_info ;
} else {
2017-04-22 09:53:28 +02:00
xwb . entry_flags = ( entry_info ) & 0xF ; /*4b*/
xwb . num_samples = ( entry_info > > 4 ) & 0x0FFFFFFF ; /*28b*/
2017-02-25 13:57:18 +01:00
}
xwb . format = ( uint32_t ) read_32bit ( off + 0x04 , streamFile ) ;
xwb . stream_offset = xwb . data_offset + ( uint32_t ) read_32bit ( off + 0x08 , streamFile ) ;
xwb . stream_size = ( uint32_t ) read_32bit ( off + 0x0c , streamFile ) ;
2009-03-12 16:28:59 +01:00
2017-04-22 09:53:28 +02:00
if ( xwb . version < = XACT2_1_MAX ) { /* LoopRegion (bytes) */
2017-02-25 19:53:21 +01:00
xwb . loop_start = ( uint32_t ) read_32bit ( off + 0x10 , streamFile ) ;
2017-04-22 09:53:28 +02:00
xwb . loop_end = ( uint32_t ) read_32bit ( off + 0x14 , streamFile ) ; //length (LoopRegion) or offset (XMALoopRegion in late XACT2)
} else { /* LoopRegion (samples) */
2017-02-25 13:57:18 +01:00
xwb . loop_start_sample = ( uint32_t ) read_32bit ( off + 0x10 , streamFile ) ;
2017-04-22 09:53:28 +02:00
xwb . loop_end_sample = ( uint32_t ) read_32bit ( off + 0x14 , streamFile ) + xwb . loop_start_sample ;
2017-02-25 13:57:18 +01:00
}
}
2009-03-12 16:28:59 +01:00
2017-02-25 13:57:18 +01:00
/* parse format */
2017-04-22 09:53:28 +02:00
if ( xwb . version < = XACT1_0_MAX ) {
xwb . bits_per_sample = ( xwb . format > > 31 ) & 0x1 ; /*1b*/
xwb . sample_rate = ( xwb . format > > 4 ) & 0x7FFFFFF ; /*27b*/
xwb . channels = ( xwb . format > > 1 ) & 0x7 ; /*3b*/
xwb . tag = ( xwb . format ) & 0x1 ; /*1b*/
2017-02-25 13:57:18 +01:00
}
2017-04-22 09:53:28 +02:00
else if ( xwb . version < = XACT1_1_MAX ) {
xwb . bits_per_sample = ( xwb . format > > 31 ) & 0x1 ; /*1b*/
xwb . sample_rate = ( xwb . format > > 5 ) & 0x3FFFFFF ; /*26b*/
xwb . channels = ( xwb . format > > 2 ) & 0x7 ; /*3b*/
xwb . tag = ( xwb . format ) & 0x3 ; /*2b*/
}
else if ( xwb . version < = XACT2_0_MAX ) {
xwb . bits_per_sample = ( xwb . format > > 31 ) & 0x1 ; /*1b*/
xwb . block_align = ( xwb . format > > 24 ) & 0xFF ; /*8b*/
xwb . sample_rate = ( xwb . format > > 4 ) & 0x7FFFF ; /*19b*/
xwb . channels = ( xwb . format > > 1 ) & 0x7 ; /*3b*/
xwb . tag = ( xwb . format ) & 0x1 ; /*1b*/
2017-02-25 13:57:18 +01:00
}
else {
2017-04-22 09:53:28 +02:00
xwb . bits_per_sample = ( xwb . format > > 31 ) & 0x1 ; /*1b*/
xwb . block_align = ( xwb . format > > 23 ) & 0xFF ; /*8b*/
xwb . sample_rate = ( xwb . format > > 5 ) & 0x3FFFF ; /*18b*/
xwb . channels = ( xwb . format > > 2 ) & 0x7 ; /*3b*/
xwb . tag = ( xwb . format ) & 0x3 ; /*2b*/
2009-03-12 16:28:59 +01:00
}
2017-02-25 13:57:18 +01:00
/* standardize tag to codec */
2017-04-22 09:53:28 +02:00
if ( xwb . version < = XACT1_0_MAX ) {
switch ( xwb . tag ) {
case 0 : xwb . codec = PCM ; break ;
case 1 : xwb . codec = XBOX_ADPCM ; break ;
default : goto fail ;
}
2017-11-10 19:31:54 +01:00
}
else if ( xwb . version < = XACT1_1_MAX ) {
2017-02-25 13:57:18 +01:00
switch ( xwb . tag ) {
case 0 : xwb . codec = PCM ; break ;
case 1 : xwb . codec = XBOX_ADPCM ; break ;
case 2 : xwb . codec = WMA ; break ;
2017-11-10 19:31:54 +01:00
case 3 : xwb . codec = OGG ; break ; /* extension */
2017-02-25 13:57:18 +01:00
default : goto fail ;
}
2017-11-10 19:31:54 +01:00
}
else if ( xwb . version < = XACT2_2_MAX ) {
2017-02-25 13:57:18 +01:00
switch ( xwb . tag ) {
case 0 : xwb . codec = PCM ; break ;
/* Table Tennis (v34): XMA1, Prey (v38): XMA2, v35/36/37: ? */
2017-04-22 09:53:28 +02:00
case 1 : xwb . codec = xwb . version < = XACT2_0_MAX ? XMA1 : XMA2 ; break ;
2017-02-25 13:57:18 +01:00
case 2 : xwb . codec = MS_ADPCM ; break ;
default : goto fail ;
}
2017-11-10 19:31:54 +01:00
}
else {
2017-02-25 13:57:18 +01:00
switch ( xwb . tag ) {
case 0 : xwb . codec = PCM ; break ;
case 1 : xwb . codec = XMA2 ; break ;
case 2 : xwb . codec = MS_ADPCM ; break ;
case 3 : xwb . codec = XWMA ; break ;
default : goto fail ;
}
}
2017-03-18 00:22:20 +01:00
/* Techland's bizarre format hijack (Nail'd, Sniper: Ghost Warrior PS3).
* Somehow they used XWB + ATRAC3 in their PS3 games , very creative */
2017-04-22 09:53:28 +02:00
if ( xwb . version = = XACT_TECHLAND & & xwb . codec = = XMA2 /* XACT_TECHLAND used in their X360 games too */
2017-03-18 00:22:20 +01:00
& & ( xwb . block_align = = 0x60 | | xwb . block_align = = 0x98 | | xwb . block_align = = 0xc0 ) ) {
xwb . codec = ATRAC3 ; /* standard ATRAC3 blocks sizes; no other way to identify (other than reading data) */
/* num samples uses a modified entry_info format (maybe skip samples + samples? sfx use the standard format)
2017-04-22 09:53:28 +02:00
* ignore for now and just calc max samples */
2017-04-07 21:18:07 +02:00
xwb . num_samples = atrac3_bytes_to_samples ( xwb . stream_size , xwb . block_align * xwb . channels ) ;
2017-03-18 00:22:20 +01:00
}
2017-11-10 19:31:54 +01:00
/* Oddworld: Stranger's Wrath iOS/Android format hijack, with changed meanings */
if ( xwb . codec = = OGG ) {
xwb . num_samples = xwb . stream_size / ( 2 * xwb . channels ) ; /* uncompressed bytes */
xwb . stream_size = xwb . loop_end ;
xwb . loop_start = 0 ;
xwb . loop_end = 0 ;
}
/* test loop after the above fixes */
xwb . loop_flag = ( xwb . loop_end > 0 | | xwb . loop_end_sample > xwb . loop_start )
& & ! ( xwb . entry_flags & WAVEBANKENTRY_FLAGS_IGNORELOOP ) ;
if ( xwb . codec ! = OGG ) {
/* for Oddworld OGG the data_size value is size of uncompressed bytes instead */
/* some BlazBlue Centralfiction songs have padding after data size (maybe wrong rip?) */
if ( xwb . data_offset + xwb . data_size > get_streamfile_size ( streamFile ) )
goto fail ;
}
2017-02-25 13:57:18 +01:00
/* fix samples */
2017-04-22 09:53:28 +02:00
if ( xwb . version < = XACT2_2_MAX & & xwb . codec = = PCM ) {
int bits_per_sample = xwb . bits_per_sample = = 0 ? 8 : 16 ;
xwb . num_samples = pcm_bytes_to_samples ( xwb . stream_size , xwb . channels , bits_per_sample ) ;
2017-02-25 13:57:18 +01:00
if ( xwb . loop_flag ) {
2017-04-22 09:53:28 +02:00
xwb . loop_start_sample = pcm_bytes_to_samples ( xwb . loop_start , xwb . channels , bits_per_sample ) ;
xwb . loop_end_sample = pcm_bytes_to_samples ( xwb . loop_start + xwb . loop_end , xwb . channels , bits_per_sample ) ;
2017-02-25 13:57:18 +01:00
}
2017-02-25 19:53:21 +01:00
}
2017-04-22 09:53:28 +02:00
else if ( xwb . version < = XACT1_1_MAX & & xwb . codec = = XBOX_ADPCM ) {
xwb . block_align = 0x24 * xwb . channels ;
xwb . num_samples = ms_ima_bytes_to_samples ( xwb . stream_size , xwb . block_align , xwb . channels ) ;
2017-02-25 13:57:18 +01:00
if ( xwb . loop_flag ) {
2017-04-22 09:53:28 +02:00
xwb . loop_start_sample = ms_ima_bytes_to_samples ( xwb . loop_start , xwb . block_align , xwb . channels ) ;
xwb . loop_end_sample = ms_ima_bytes_to_samples ( xwb . loop_start + xwb . loop_end , xwb . block_align , xwb . channels ) ;
2017-02-25 13:57:18 +01:00
}
2017-02-25 19:53:21 +01:00
}
2017-04-22 09:53:28 +02:00
else if ( xwb . version < = XACT2_2_MAX & & xwb . codec = = MS_ADPCM & & xwb . loop_flag ) {
int block_size = ( xwb . block_align + 22 ) * xwb . channels ; /*22=CONVERSION_OFFSET (?)*/
2017-02-25 19:53:21 +01:00
2017-04-22 09:53:28 +02:00
xwb . loop_start_sample = msadpcm_bytes_to_samples ( xwb . loop_start , block_size , xwb . channels ) ;
xwb . loop_end_sample = msadpcm_bytes_to_samples ( xwb . loop_start + xwb . loop_end , block_size , xwb . channels ) ;
2017-02-25 19:53:21 +01:00
}
2017-04-22 09:53:28 +02:00
else if ( xwb . version < = XACT2_1_MAX & & ( xwb . codec = = XMA1 | | xwb . codec = = XMA2 ) & & xwb . loop_flag ) {
/* v38: byte offset, v40+: sample offset, v39: ? */
2017-02-25 19:53:21 +01:00
/* need to manually find sample offsets, thanks to Microsoft dumb headers */
2017-04-15 23:58:19 +02:00
ms_sample_data msd ;
memset ( & msd , 0 , sizeof ( ms_sample_data ) ) ;
msd . xma_version = xwb . codec = = XMA1 ? 1 : 2 ;
2017-04-22 09:53:28 +02:00
msd . channels = xwb . channels ;
2017-04-15 23:58:19 +02:00
msd . data_offset = xwb . stream_offset ;
2017-04-22 09:53:28 +02:00
msd . data_size = xwb . stream_size ;
msd . loop_flag = xwb . loop_flag ;
2017-04-15 23:58:19 +02:00
msd . loop_start_b = xwb . loop_start ; /* bit offset in the stream */
msd . loop_end_b = ( xwb . loop_end > > 4 ) ; /*28b */
2017-02-25 19:53:21 +01:00
/* XACT adds +1 to the subframe, but this means 0 can't be used? */
2017-04-15 23:58:19 +02:00
msd . loop_end_subframe = ( ( xwb . loop_end > > 2 ) & 0x3 ) + 1 ; /* 2b */
msd . loop_start_subframe = ( ( xwb . loop_end > > 0 ) & 0x3 ) + 1 ; /* 2b */
2017-02-25 19:53:21 +01:00
2017-04-15 23:58:19 +02:00
xma_get_samples ( & msd , streamFile ) ;
xwb . loop_start_sample = msd . loop_start_sample ;
2017-04-22 09:53:28 +02:00
xwb . loop_end_sample = msd . loop_end_sample ;
2017-03-05 17:34:37 +01:00
2017-02-25 19:53:21 +01:00
// todo fix properly (XWB loop_start/end seem to count padding samples while XMA1 RIFF doesn't)
2017-05-18 22:14:32 +02:00
//this doesn't seem ok because can fall within 0 to 512 (ie.- first frame, 384)
2017-03-05 17:34:37 +01:00
//if (xwb.loop_start_sample) xwb.loop_start_sample -= 512;
//if (xwb.loop_end_sample) xwb.loop_end_sample -= 512;
//add padding back until it's fixed (affects looping)
// (in rare cases this causes a glitch in FFmpeg since it has a bug where it's missing some samples)
xwb . num_samples + = 64 + 512 ;
2017-02-25 13:57:18 +01:00
}
2017-05-18 22:14:32 +02:00
else if ( ( xwb . codec = = XMA1 | | xwb . codec = = XMA2 ) & & xwb . loop_flag ) {
/* seems to be needed by some edge cases, ex. Crackdown */
//add padding, see above
xwb . num_samples + = 64 + 512 ;
}
2017-02-25 13:57:18 +01:00
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream ( xwb . channels , xwb . loop_flag ) ;
if ( ! vgmstream ) goto fail ;
vgmstream - > sample_rate = xwb . sample_rate ;
vgmstream - > num_samples = xwb . num_samples ;
vgmstream - > loop_start_sample = xwb . loop_start_sample ;
2017-04-22 09:53:28 +02:00
vgmstream - > loop_end_sample = xwb . loop_end_sample ;
2017-03-04 02:09:00 +01:00
vgmstream - > num_streams = xwb . streams ;
2018-01-28 00:41:25 +01:00
vgmstream - > stream_size = xwb . stream_size ;
2009-03-12 16:28:59 +01:00
vgmstream - > meta_type = meta_XWB ;
2017-08-12 11:46:28 +02:00
get_xsb_name ( vgmstream - > stream_name , STREAM_NAME_SIZE , target_stream , & xwb , streamFile ) ;
2009-03-12 16:28:59 +01:00
2017-02-25 13:57:18 +01:00
switch ( xwb . codec ) {
2018-01-27 10:57:29 +01:00
case PCM : /* Unreal Championship (Xbox)[PCM8], KOF2003 (Xbox)[PCM16LE], Otomedius (X360)[PCM16BE] */
vgmstream - > coding_type = xwb . bits_per_sample = = 0 ? coding_PCM8_U :
2017-02-25 13:57:18 +01:00
( xwb . little_endian ? coding_PCM16LE : coding_PCM16BE ) ;
vgmstream - > layout_type = xwb . channels > 1 ? layout_interleave : layout_none ;
vgmstream - > interleave_block_size = xwb . bits_per_sample = = 0 ? 0x01 : 0x02 ;
break ;
2018-01-27 10:57:29 +01:00
case XBOX_ADPCM : /* Silent Hill 4 (Xbox) */
2017-02-25 13:57:18 +01:00
vgmstream - > coding_type = coding_XBOX ;
vgmstream - > layout_type = layout_none ;
break ;
2018-01-27 10:57:29 +01:00
case MS_ADPCM : /* Persona 4 Ultimax (AC) */
2017-02-25 13:57:18 +01:00
vgmstream - > coding_type = coding_MSADPCM ;
vgmstream - > layout_type = layout_none ;
vgmstream - > interleave_block_size = ( xwb . block_align + 22 ) * xwb . channels ; /*22=CONVERSION_OFFSET (?)*/
break ;
2009-03-12 16:28:59 +01:00
2017-02-25 13:57:18 +01:00
# ifdef VGM_USE_FFMPEG
2018-01-27 10:57:29 +01:00
case XMA1 : { /* Kameo (X360) */
2017-02-25 13:57:18 +01:00
uint8_t buf [ 100 ] ;
int bytes ;
2009-03-12 16:28:59 +01:00
2017-02-25 17:29:25 +01:00
bytes = ffmpeg_make_riff_xma1 ( buf , 100 , vgmstream - > num_samples , xwb . stream_size , vgmstream - > channels , vgmstream - > sample_rate , 0 ) ;
2017-02-25 13:57:18 +01:00
if ( bytes < = 0 ) goto fail ;
2018-01-27 10:57:29 +01:00
vgmstream - > codec_data = init_ffmpeg_header_offset ( streamFile , buf , bytes , xwb . stream_offset , xwb . stream_size ) ;
if ( ! vgmstream - > codec_data ) goto fail ;
2017-02-25 13:57:18 +01:00
vgmstream - > coding_type = coding_FFmpeg ;
vgmstream - > layout_type = layout_none ;
break ;
2009-03-12 16:28:59 +01:00
}
2017-02-25 13:57:18 +01:00
2018-01-27 10:57:29 +01:00
case XMA2 : { /* Blue Dragon (X360) */
2017-02-25 13:57:18 +01:00
uint8_t buf [ 100 ] ;
int bytes , block_size , block_count ;
block_size = 0x10000 ; /* XACT default */
block_count = xwb . stream_size / block_size + ( xwb . stream_size % block_size ? 1 : 0 ) ;
bytes = ffmpeg_make_riff_xma2 ( buf , 100 , vgmstream - > num_samples , xwb . stream_size , vgmstream - > channels , vgmstream - > sample_rate , block_count , block_size ) ;
if ( bytes < = 0 ) goto fail ;
2018-01-27 10:57:29 +01:00
vgmstream - > codec_data = init_ffmpeg_header_offset ( streamFile , buf , bytes , xwb . stream_offset , xwb . stream_size ) ;
if ( ! vgmstream - > codec_data ) goto fail ;
2017-02-25 13:57:18 +01:00
vgmstream - > coding_type = coding_FFmpeg ;
vgmstream - > layout_type = layout_none ;
break ;
}
2018-01-27 10:57:29 +01:00
case WMA : { /* WMAudio1 (WMA v1): Prince of Persia 2 port (Xbox) */
2017-02-25 13:57:18 +01:00
ffmpeg_codec_data * ffmpeg_data = NULL ;
ffmpeg_data = init_ffmpeg_offset ( streamFile , xwb . stream_offset , xwb . stream_size ) ;
if ( ! ffmpeg_data ) goto fail ;
vgmstream - > codec_data = ffmpeg_data ;
vgmstream - > coding_type = coding_FFmpeg ;
vgmstream - > layout_type = layout_none ;
2017-10-28 01:31:08 +02:00
/* no wma_bytes_to_samples, this should be ok */
if ( ! vgmstream - > num_samples )
vgmstream - > num_samples = ffmpeg_data - > totalSamples ;
2017-02-25 13:57:18 +01:00
break ;
}
2018-01-27 10:57:29 +01:00
case XWMA : { /* WMAudio2 (WMA v2): BlazBlue (X360), WMAudio3 (WMA Pro): ? */
2017-02-25 13:57:18 +01:00
uint8_t buf [ 100 ] ;
int bytes , bps_index , block_align , block_index , avg_bps , wma_codec ;
bps_index = ( xwb . block_align > > 5 ) ; /* upper 3b bytes-per-second index */ //docs say 2b+6b but are wrong
block_index = ( xwb . block_align ) & 0x1F ; /*lower 5b block alignment index */
if ( bps_index > = 7 ) goto fail ;
if ( block_index > = 17 ) goto fail ;
avg_bps = wma_avg_bps_index [ bps_index ] ;
block_align = wma_block_align_index [ block_index ] ;
wma_codec = xwb . bits_per_sample ? 0x162 : 0x161 ; /* 0=WMAudio2, 1=WMAudio3 */
2017-04-07 20:21:55 +02:00
bytes = ffmpeg_make_riff_xwma ( buf , 100 , wma_codec , xwb . stream_size , vgmstream - > channels , vgmstream - > sample_rate , avg_bps , block_align ) ;
2017-02-25 13:57:18 +01:00
if ( bytes < = 0 ) goto fail ;
2018-01-27 10:57:29 +01:00
vgmstream - > codec_data = init_ffmpeg_header_offset ( streamFile , buf , bytes , xwb . stream_offset , xwb . stream_size ) ;
if ( ! vgmstream - > codec_data ) goto fail ;
2017-02-25 13:57:18 +01:00
vgmstream - > coding_type = coding_FFmpeg ;
vgmstream - > layout_type = layout_none ;
break ;
}
2017-03-18 00:22:20 +01:00
2018-01-27 10:57:29 +01:00
case ATRAC3 : { /* Techland PS3 extension: Sniper Ghost Warrior (PS3) */
2017-03-18 00:22:20 +01:00
uint8_t buf [ 200 ] ;
int bytes ;
int block_size = xwb . block_align * vgmstream - > channels ;
int joint_stereo = xwb . block_align = = 0x60 ; /* untested, ATRAC3 default */
int skip_samples = 0 ; /* unknown */
bytes = ffmpeg_make_riff_atrac3 ( buf , 200 , vgmstream - > num_samples , xwb . stream_size , vgmstream - > channels , vgmstream - > sample_rate , block_size , joint_stereo , skip_samples ) ;
if ( bytes < = 0 ) goto fail ;
vgmstream - > codec_data = init_ffmpeg_header_offset ( streamFile , buf , bytes , xwb . stream_offset , xwb . stream_size ) ;
if ( ! vgmstream - > codec_data ) goto fail ;
vgmstream - > coding_type = coding_FFmpeg ;
vgmstream - > layout_type = layout_none ;
break ;
}
2017-11-10 19:31:54 +01:00
case OGG : { /* Oddworld: Strangers Wrath iOS/Android extension */
vgmstream - > codec_data = init_ffmpeg_offset ( streamFile , xwb . stream_offset , xwb . stream_size ) ;
if ( ! vgmstream - > codec_data ) goto fail ;
vgmstream - > coding_type = coding_FFmpeg ;
vgmstream - > layout_type = layout_none ;
break ;
}
2017-02-25 13:57:18 +01:00
# endif
default :
goto fail ;
2009-03-12 16:28:59 +01:00
}
2017-02-25 13:57:18 +01:00
2017-09-30 01:52:49 +02:00
start_offset = xwb . stream_offset ;
2017-02-25 13:57:18 +01:00
if ( ! vgmstream_open_stream ( vgmstream , streamFile , start_offset ) )
goto fail ;
2009-03-12 16:28:59 +01:00
return vgmstream ;
fail :
2017-02-25 13:57:18 +01:00
close_vgmstream ( vgmstream ) ;
2009-03-12 16:28:59 +01:00
return NULL ;
}
2017-08-12 11:46:28 +02:00
/* ****************************************************************************** */
/* 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 void get_xsb_name ( char * buf , size_t maxsize , int target_stream , xwb_header * xwb , STREAMFILE * streamXwb ) {
2017-10-28 01:31:08 +02:00
STREAMFILE * streamFile = NULL ;
2017-08-12 11:46:28 +02:00
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 ;
memset ( & xsb , 0 , sizeof ( xsb_header ) ) ; /* before any "fail"! */
streamFile = open_stream_ext ( streamXwb , " xsb " ) ;
if ( ! streamFile ) goto fail ;
//todo try common names (xwb and xsb often are named slightly differently using a common convention)
/* check header */
if ( ( read_32bitBE ( 0x00 , streamFile ) ! = 0x5344424B ) & & /* "SDBK" (LE) */
( read_32bitBE ( 0x00 , streamFile ) ! = 0x4B424453 ) ) /* "KBDS" (BE) */
goto fail ;
if ( read_32bitBE ( 0x00 , streamFile ) = = 0x5344424B ) { /* SDBK */
read_32bit = read_32bitLE ;
read_16bit = read_16bitLE ;
} 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 ) ) {
2017-10-28 01:31:08 +02:00
VGM_LOG ( " XSB: xsb and xwb are from different XACT versions (xsb v%i vs xwb v%i) \n " , xsb_version , xwb - > version ) ;
2017-08-12 11:46:28 +02:00
goto fail ;
}
off = 0 ;
if ( xsb_version < = XSB_XACT1_MAX ) {
xsb . xsb_wavebanks_count = 1 ; //read_8bit(0x22, streamFile);
xsb . xsb_sounds_count = 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 = read_16bit ( 0x09 , streamFile ) ;
xsb . xsb_complex_sounds_count = read_16bit ( 0x0B , streamFile ) ;
xsb . xsb_wavebanks_count = read_8bit ( 0x11 , streamFile ) ;
xsb . xsb_sounds_count = 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 = read_16bit ( 0x13 , streamFile ) ;
xsb . xsb_complex_sounds_count = read_16bit ( 0x15 , streamFile ) ;
xsb . xsb_wavebanks_count = 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 - > streams ,
2017-10-28 01:31:08 +02:00
" XSB: number of streams in xsb lower than xwb (xsb %i vs xwb %i) \n " , xsb . xsb_sounds_count , xwb - > streams ) ;
2017-08-12 11:46:28 +02:00
VGM_ASSERT ( xsb . xsb_simple_sounds_count + xsb . xsb_complex_sounds_count ! = xsb . xsb_sounds_count ,
2017-10-28 01:31:08 +02:00
" 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 ) ;
2017-08-12 11:46:28 +02:00
/* 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 */
/* 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 ) {
2017-10-28 01:31:08 +02:00
VGM_LOG ( " XSB: xsb flag 0x%x at offset 0x%08lx not implemented \n " , flag , off ) ;
2017-08-12 11:46:28 +02:00
goto fail ;
}
s - > wavebank = 0 ; //read_8bit(off+suboff + 0x02, streamFile);
s - > stream_index = read_16bit ( off + 0x02 , streamFile ) ;
s - > sound_offset = off ;
s - > name_offset = 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 = 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 & & read_16bit ( off + size - 0x2 , streamFile ) ! = 0 ) {
suboff = size - 0x08 - 0x07 ; //7 unk bytes at the end
} else {
suboff = size - 0x08 ;
}
} else {
2017-10-28 01:31:08 +02:00
VGM_LOG ( " XSB: xsb flag 0x%x at offset 0x%08lx not implemented \n " , flag , off ) ;
2017-08-12 11:46:28 +02:00
goto fail ;
}
}
s - > stream_index = read_16bit ( off + suboff + 0x00 , streamFile ) ;
s - > wavebank = read_8bit ( off + suboff + 0x02 , streamFile ) ;
s - > sound_offset = off ;
}
if ( s - > wavebank + 1 > xsb . xsb_wavebanks_count ) {
2017-10-28 01:31:08 +02:00
VGM_LOG ( " XSB: unknown xsb wavebank id %i at offset 0x%lx \n " , s - > wavebank , off ) ;
2017-08-12 11:46:28 +02:00
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 - > streams ) {
if ( ! cfg__selected_wavebank ) {
2017-10-28 01:31:08 +02:00
VGM_LOG ( " XSB: multiple xsb wavebanks with the same number of sounds, use -w to specify one of the wavebanks \n " ) ;
2017-08-12 11:46:28 +02:00
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 ) {
2017-10-28 01:31:08 +02:00
VGM_LOG ( " XSB: multiple xsb wavebanks but autodetect didn't work \n " ) ;
2017-08-12 11:46:28 +02:00
goto fail ;
}
if ( xsb . xsb_wavebanks [ cfg__selected_wavebank - 1 ] . sound_count = = 0 ) {
2017-10-28 01:31:08 +02:00
VGM_LOG ( " XSB: xsb selected wavebank %i has no sounds \n " , cfg__selected_wavebank ) ;
2017-08-12 11:46:28 +02:00
goto fail ;
}
if ( cfg__start_sound ) {
if ( xsb . xsb_wavebanks [ cfg__selected_wavebank - 1 ] . sound_count - ( cfg__start_sound - 1 ) < xwb - > streams ) {
2017-10-28 01:31:08 +02:00
VGM_LOG ( " XSB: starting sound too high (max in selected wavebank is %i) \n " , xsb . xsb_wavebanks [ cfg__selected_wavebank - 1 ] . sound_count - xwb - > streams + 1 ) ;
2017-08-12 11:46:28 +02:00
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 - > streams_count ) ;
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 - > streams_count ) ;
*/
//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->streams_count);
}
/* *************************** */
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_stream - 1 ) {
name_offset = s - > name_offset ;
break ;
}
}
if ( name_offset )
read_string ( buf , maxsize , name_offset , streamFile ) ;
//return; /* no return, let free */
fail :
free ( xsb . xsb_sounds ) ;
free ( xsb . xsb_wavebanks ) ;
2017-10-28 10:51:55 +02:00
if ( streamFile ) close_streamfile ( streamFile ) ;
2017-08-12 11:46:28 +02:00
return ;
}