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"
2018-05-05 23:06:59 +02:00
# include <string.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
} ;
2018-05-27 17:30:59 +02:00
typedef enum { PCM , XBOX_ADPCM , MS_ADPCM , XMA1 , XMA2 , WMA , XWMA , ATRAC3 , OGG , DSP , ATRAC9_RIFF } 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 ;
2018-03-25 20:01:45 +02:00
off_t names_offset ;
size_t names_size ;
size_t names_entry_size ;
off_t extra_offset ;
size_t extra_size ;
2017-02-25 13:57:18 +01:00
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 ;
2018-03-08 22:51:50 +01:00
int total_subsongs ;
2017-02-25 13:57:18 +01:00
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 ;
2018-03-25 20:01:45 +02:00
static void get_name ( char * buf , size_t maxsize , int target_subsong , xwb_header * xwb , STREAMFILE * streamFile ) ;
2018-05-27 17:30:59 +02:00
static STREAMFILE * setup_subfile_streamfile ( STREAMFILE * streamFile , off_t subfile_offset , size_t subfile_size , const char * fake_ext ) ;
2017-08-12 11:46:28 +02:00
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 ;
2018-03-08 22:51:50 +01:00
xwb_header xwb = { 0 } ;
int target_subsong = streamFile - > stream_index ;
2017-02-25 13:57:18 +01:00
int32_t ( * read_32bit ) ( off_t , STREAMFILE * ) = NULL ;
2018-03-08 22:51:50 +01:00
/* checks */
2018-08-25 12:21:21 +02:00
/* .xwb: standard
* . xna : Touhou Makukasai ~ Fantasy Danmaku Festival ( PC ) */
if ( ! check_extensions ( streamFile , " xwb,xna " ) )
2018-03-08 22:51:50 +01:00
goto fail ;
2017-02-25 13:57:18 +01:00
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
2018-08-25 12:21:21 +02:00
xwb . little_endian = read_32bitBE ( 0x00 , streamFile ) = = 0x57424E44 ; /* WBND */
2017-02-25 13:57:18 +01:00
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 ) {
2018-03-08 22:51:50 +01:00
xwb . total_subsongs = read_32bit ( 0x0c , streamFile ) ;
2018-03-25 20:01:45 +02:00
/* 0x10: bank name (size 0x10) */
xwb . base_offset = 0 ;
xwb . base_size = 0 ;
xwb . entry_offset = 0x50 ;
2017-04-22 09:53:28 +02:00
xwb . entry_elem_size = 0x14 ;
2018-07-27 17:12:09 +02:00
xwb . entry_size = xwb . entry_elem_size * xwb . total_subsongs ;
2018-03-25 20:01:45 +02:00
xwb . data_offset = xwb . entry_offset + xwb . entry_size ;
xwb . data_size = get_streamfile_size ( streamFile ) - xwb . data_offset ;
xwb . names_offset = 0 ;
xwb . names_size = 0 ;
xwb . names_entry_size = 0 ;
xwb . extra_offset = 0 ;
xwb . extra_size = 0 ;
2017-04-22 09:53:28 +02:00
}
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 ) ;
2018-03-25 20:01:45 +02:00
/* read extra segments (values can be 0 == no segment) */
if ( xwb . version < = XACT1_1_MAX ) {
xwb . names_offset = read_32bit ( off + 0x10 , streamFile ) ; //ENTRYNAMES
xwb . names_size = read_32bit ( off + 0x14 , streamFile ) ;
xwb . names_entry_size = 0x40 ;
xwb . extra_offset = 0 ;
xwb . extra_size = 0 ;
suboff = 0x04 * 2 ;
}
else if ( xwb . version < = XACT2_1_MAX ) {
xwb . names_offset = read_32bit ( off + 0x10 , streamFile ) ; //ENTRYNAMES
xwb . names_size = read_32bit ( off + 0x14 , streamFile ) ;
xwb . names_entry_size = 0x40 ;
xwb . extra_offset = read_32bit ( off + 0x18 , streamFile ) ; //EXTRA
xwb . extra_size = read_32bit ( off + 0x1c , streamFile ) ;
suboff = 0x04 * 2 + 0x04 * 2 ;
} else {
xwb . extra_offset = read_32bit ( off + 0x10 , streamFile ) ; //SEEKTABLES
xwb . extra_size = read_32bit ( off + 0x14 , streamFile ) ;
xwb . names_offset = read_32bit ( off + 0x18 , streamFile ) ; //ENTRYNAMES
xwb . names_size = read_32bit ( off + 0x1c , streamFile ) ;
xwb . names_entry_size = 0x40 ;
suboff = 0x04 * 2 + 0x04 * 2 ;
}
2017-04-22 09:53:28 +02:00
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 ) ;
2018-03-08 22:51:50 +01:00
xwb . total_subsongs = read_32bit ( off + 0x04 , streamFile ) ;
2018-03-25 20:01:45 +02:00
/* 0x08: bank name (size 0x40) */
2017-04-22 09:53:28 +02:00
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
2018-03-08 22:51:50 +01:00
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 ;
2009-03-12 16:28:59 +01:00
2017-02-25 13:57:18 +01:00
/* read stream entry (WAVEBANKENTRY) */
2018-03-08 22:51:50 +01:00
off = 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)] */
uint32_t entry , size_deviation , sector_offset ;
off_t next_stream_offset ;
entry = ( uint32_t ) read_32bit ( off + 0x00 , streamFile ) ;
size_deviation = ( ( entry > > 21 ) & 0x7FF ) ; /* 11b, padding data for sector alignment in bytes*/
sector_offset = ( entry & 0x1FFFFF ) ; /* 21b, offset within data in sectors */
xwb . stream_offset = xwb . data_offset + sector_offset * xwb . entry_alignment ;
/* find size using next offset */
if ( target_subsong < xwb . total_subsongs ) {
uint32_t next_entry = ( uint32_t ) read_32bit ( off + 0x04 , streamFile ) ;
next_stream_offset = xwb . data_offset + ( next_entry & 0x1FFFFF ) * xwb . entry_alignment ;
}
else { /* for last entry (or first, when subsongs = 1) */
next_stream_offset = xwb . data_offset + xwb . data_size ;
2017-02-25 13:57:18 +01:00
}
2018-03-08 22:51:50 +01:00
xwb . stream_size = next_stream_offset - xwb . stream_offset - size_deviation ;
2017-02-25 13:57:18 +01:00
}
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 ;
}
}
2018-05-27 17:30:59 +02:00
/* format hijacks from creative devs, using non-official codecs */
2017-04-22 09:53:28 +02:00
if ( xwb . version = = XACT_TECHLAND & & xwb . codec = = XMA2 /* XACT_TECHLAND used in their X360 games too */
2018-05-27 17:30:59 +02:00
& & ( xwb . block_align = = 0x60 | | xwb . block_align = = 0x98 | | xwb . block_align = = 0xc0 ) ) { /* standard ATRAC3 blocks sizes */
/* Techland ATRAC3 [Nail'd (PS3), Sniper: Ghost Warrior (PS3)] */
xwb . codec = ATRAC3 ;
2017-03-18 00:22:20 +01:00
/* 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
}
2018-05-27 17:30:59 +02:00
else if ( xwb . codec = = OGG ) {
/* Oddworld: Stranger's Wrath (iOS/Android) */
2017-11-10 19:31:54 +01:00
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 ;
}
2018-05-27 17:30:59 +02:00
else if ( xwb . version = = XACT3_0_MAX & & xwb . codec = = XMA2
& & xwb . bits_per_sample = = 0x01 & & xwb . block_align = = 0x04
& & xwb . data_size = = 0x55951c1c ) { /* some kind of id? */
/* Stardew Valley (Switch), full interleaved DSPs (including headers) */
2018-05-05 23:06:59 +02:00
xwb . codec = DSP ;
}
2018-05-27 17:30:59 +02:00
else if ( xwb . version = = XACT3_0_MAX & & xwb . codec = = XMA2
& & xwb . bits_per_sample = = 0x01 & & xwb . block_align = = 0x04
& & xwb . data_size = = 0x4e0a1000 ) { /* some kind of id? */
/* Stardew Valley (Vita), standard RIFF with ATRAC9 */
xwb . codec = ATRAC9_RIFF ;
}
2017-11-10 19:31:54 +01:00
/* 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 ) ;
2018-05-05 23:06:59 +02:00
/* Oddworld OGG the data_size value is size of uncompressed bytes instead; DSP uses some id/config as value */
2018-05-27 17:30:59 +02:00
if ( xwb . codec ! = OGG & & xwb . codec ! = DSP & & xwb . codec ! = ATRAC9_RIFF ) {
2018-05-05 23:06:59 +02:00
/* some low-q rips don't remove padding, relax validation a bit */
2017-11-10 19:31:54 +01:00
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 ) {
2018-02-17 12:30:14 +01:00
xwb . block_align = 0x24 * xwb . channels ; /* not really needed... */
xwb . num_samples = xbox_ima_bytes_to_samples ( xwb . stream_size , xwb . channels ) ;
2017-02-25 13:57:18 +01:00
if ( xwb . loop_flag ) {
2018-02-17 12:30:14 +01:00
xwb . loop_start_sample = xbox_ima_bytes_to_samples ( xwb . loop_start , xwb . channels ) ;
xwb . loop_end_sample = xbox_ima_bytes_to_samples ( xwb . loop_start + xwb . loop_end , 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 */
2018-03-25 20:01:45 +02:00
ms_sample_data msd = { 0 } ;
2017-04-15 23:58:19 +02:00
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
2018-08-12 01:23:58 +02:00
/* for XWB v22 (and below?) this seems normal [Project Gotham Racing (X360)] */
if ( xwb . num_samples = = 0 )
xwb . num_samples = msd . num_samples ;
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 ;
2018-03-08 22:51:50 +01:00
vgmstream - > num_streams = xwb . total_subsongs ;
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 ;
2018-03-25 20:01:45 +02:00
get_name ( vgmstream - > stream_name , STREAM_NAME_SIZE , target_subsong , & 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) */
2018-02-17 12:30:14 +01:00
vgmstream - > coding_type = coding_XBOX_IMA ;
2017-02-25 13:57:18 +01:00
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 )
2018-03-03 19:07:59 +01:00
vgmstream - > num_samples = ( int32_t ) ffmpeg_data - > totalSamples ;
2017-02-25 13:57:18 +01:00
break ;
}
2018-05-05 23:06:59 +02:00
case XWMA : { /* WMAudio2 (WMA v2): BlazBlue (X360), WMAudio3 (WMA Pro): Bullet Witch (PC) voices */
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-05-05 23:06:59 +02: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
2018-05-05 23:06:59 +02:00
case OGG : { /* Oddworld: Strangers Wrath (iOS/Android) extension */
2017-11-10 19:31:54 +01:00
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
2018-05-05 23:06:59 +02:00
case DSP : { /* Stardew Valley (Switch) extension */
vgmstream - > coding_type = coding_NGC_DSP ;
vgmstream - > layout_type = layout_interleave ;
vgmstream - > interleave_block_size = xwb . stream_size / xwb . channels ;
dsp_read_coefs ( vgmstream , streamFile , xwb . stream_offset + 0x1c , vgmstream - > interleave_block_size , ! xwb . little_endian ) ;
dsp_read_hist ( vgmstream , streamFile , xwb . stream_offset + 0x3c , vgmstream - > interleave_block_size , ! xwb . little_endian ) ;
xwb . stream_offset + = 0x60 ; /* skip DSP header */
break ;
}
2018-05-27 17:30:59 +02:00
# ifdef VGM_USE_ATRAC9
case ATRAC9_RIFF : { /* Stardew Valley (Vita) extension */
VGMSTREAM * temp_vgmstream = NULL ;
STREAMFILE * temp_streamFile = NULL ;
/* standard RIFF, use subfile (seems doesn't use xwb loops) */
VGM_ASSERT ( xwb . loop_flag , " XWB: RIFF ATRAC9 loop flag found \n " ) ;
temp_streamFile = setup_subfile_streamfile ( streamFile , xwb . stream_offset , xwb . stream_size , " at9 " ) ;
if ( ! temp_streamFile ) goto fail ;
temp_vgmstream = init_vgmstream_riff ( temp_streamFile ) ;
close_streamfile ( temp_streamFile ) ;
if ( ! temp_vgmstream ) goto fail ;
temp_vgmstream - > num_streams = vgmstream - > num_streams ;
temp_vgmstream - > stream_size = vgmstream - > stream_size ;
temp_vgmstream - > meta_type = vgmstream - > meta_type ;
close_vgmstream ( vgmstream ) ;
return temp_vgmstream ;
}
# endif
2017-02-25 13:57:18 +01:00
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
2018-05-27 17:30:59 +02:00
/* ****************************************************************************** */
static STREAMFILE * setup_subfile_streamfile ( STREAMFILE * streamFile , off_t subfile_offset , size_t subfile_size , const char * fake_ext ) {
STREAMFILE * temp_streamFile = NULL , * new_streamFile = NULL ;
/* setup subfile */
new_streamFile = open_wrap_streamfile ( streamFile ) ;
if ( ! new_streamFile ) goto fail ;
temp_streamFile = new_streamFile ;
new_streamFile = open_clamp_streamfile ( temp_streamFile , subfile_offset , subfile_size ) ;
if ( ! new_streamFile ) goto fail ;
temp_streamFile = new_streamFile ;
new_streamFile = open_fakename_streamfile ( temp_streamFile , NULL , fake_ext ) ;
if ( ! new_streamFile ) goto fail ;
temp_streamFile = new_streamFile ;
return temp_streamFile ;
fail :
close_streamfile ( temp_streamFile ) ;
return NULL ;
}
2017-08-12 11:46:28 +02:00
2018-03-25 20:01:45 +02:00
/* ****************************************************************************** */
/* 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 ) {
2018-03-29 20:42:52 +02:00
size_t read ;
2018-03-25 20:01:45 +02:00
if ( ! xwb - > names_offset | | ! xwb - > names_size | | xwb - > names_entry_size > maxsize )
goto fail ;
read = read_string ( buf , xwb - > names_entry_size , xwb - > names_offset + xwb - > names_entry_size * ( target_subsong - 1 ) , streamFile ) ;
2018-03-29 20:42:52 +02:00
if ( read = = 0 ) goto fail ;
2018-03-25 20:01:45 +02:00
return 1 ;
fail :
return 0 ;
}
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. */
2018-05-05 23:06:59 +02:00
static int get_xsb_name ( char * buf , size_t maxsize , int target_subsong , xwb_header * xwb , STREAMFILE * streamXwb , char * filename ) {
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 ;
2018-03-25 20:01:45 +02:00
xsb_header xsb = { 0 } ;
2017-08-12 11:46:28 +02:00
2018-05-05 23:06:59 +02:00
if ( filename )
streamFile = open_streamfile_by_filename ( streamXwb , filename ) ;
else
streamFile = open_streamfile_by_ext ( streamXwb , " xsb " ) ;
2017-08-12 11:46:28 +02:00
if ( ! streamFile ) goto fail ;
/* 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 ) {
2018-08-15 00:49:17 +02:00
xsb . xsb_wavebanks_count = 1 ; //(uint8_t)read_8bit(0x22, streamFile);
xsb . xsb_sounds_count = ( uint16_t ) read_16bit ( 0x1e , streamFile ) ; //@ 0x1a? 0x1c?
2017-08-12 11:46:28 +02:00
//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 ) {
2018-08-15 00:49:17 +02:00
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 ) ;
2017-08-12 11:46:28 +02:00
//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 {
2018-08-15 00:49:17 +02:00
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 ) ;
2017-08-12 11:46:28 +02:00
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 ) ;
}
2018-03-08 22:51:50 +01:00
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 ) ;
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 ;
}
2018-08-15 00:49:17 +02:00
s - > wavebank = 0 ; //(uint8_t)read_8bit(off+suboff + 0x02, streamFile);
s - > stream_index = ( uint16_t ) read_16bit ( off + 0x02 , streamFile ) ;
2017-08-12 11:46:28 +02:00
s - > sound_offset = off ;
2018-08-15 00:49:17 +02:00
s - > name_offset = ( uint16_t ) read_16bit ( off + 0x04 , streamFile ) ;
2017-08-12 11:46:28 +02:00
}
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
2018-08-15 00:49:17 +02:00
size = ( uint16_t ) read_16bit ( off + 0x07 , streamFile ) ;
2017-08-12 11:46:28 +02:00
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 ;
2018-08-15 00:49:17 +02:00
} else if ( size % 2 = = 1 & & ( uint16_t ) read_16bit ( off + size - 0x2 , streamFile ) ! = 0 ) {
2017-08-12 11:46:28 +02:00
suboff = size - 0x08 - 0x07 ; //7 unk bytes at the end
} else {
suboff = size - 0x08 ;
}
2018-05-05 23:06:59 +02:00
//} else if (flag==0x11) { /* Stardew Valley (Switch) */
// suboff = size; //???
2017-08-12 11:46:28 +02:00
} else {
2018-05-05 23:06:59 +02:00
VGM_LOG ( " XSB: xsb flag 0x%x (size=%x) at offset 0x%08lx not implemented \n " , flag , size , off ) ;
2017-08-12 11:46:28 +02:00
goto fail ;
}
}
2018-08-15 00:49:17 +02:00
s - > stream_index = ( uint16_t ) read_16bit ( off + suboff + 0x00 , streamFile ) ;
s - > wavebank = ( uint8_t ) read_8bit ( off + suboff + 0x02 , streamFile ) ;
2017-08-12 11:46:28 +02:00
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
2018-03-08 22:51:50 +01:00
if ( w - > sound_count = = xwb - > total_subsongs ) {
2017-08-12 11:46:28 +02:00
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 ) {
2018-03-08 22:51:50 +01:00
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 ) ;
2017-08-12 11:46:28 +02:00
goto fail ;
}
} else {
/*
if ( ! cfg - > ignore_names_not_found )
2018-03-08 22:51:50 +01:00
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 ) ;
2017-08-12 11:46:28 +02:00
if ( ! cfg - > ignore_names_not_found )
2018-03-08 22:51:50 +01:00
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 ) ;
2017-08-12 11:46:28 +02:00
*/
//if (!cfg->ignore_names_not_found)
2018-03-08 22:51:50 +01:00
// 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);
2017-08-12 11:46:28 +02:00
}
/* *************************** */
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
2018-03-08 22:51:50 +01:00
& & s - > stream_index = = target_subsong - 1 ) {
2017-08-12 11:46:28 +02:00
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 ) ;
2018-03-25 20:01:45 +02:00
close_streamfile ( streamFile ) ;
return ( name_offset ) ;
}
static void get_name ( char * buf , size_t maxsize , int target_subsong , xwb_header * xwb , STREAMFILE * streamFile ) {
int name_found ;
2018-05-05 23:06:59 +02:00
char xwb_filename [ PATH_LIMIT ] ;
char xsb_filename [ PATH_LIMIT ] ;
2018-03-25 20:01:45 +02:00
/* try inside this xwb */
name_found = get_xwb_name ( buf , maxsize , target_subsong , xwb , streamFile ) ;
if ( name_found ) return ;
2018-05-05 23:06:59 +02:00
/* try again in external .xsb, using a bunch of possible name pairs */
get_streamfile_filename ( streamFile , xwb_filename , PATH_LIMIT ) ;
if ( strcmp ( xwb_filename , " Wave Bank.xwb " ) = = 0 ) {
strcpy ( xsb_filename , " Sound Bank.xsb " ) ;
}
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 ;
}
2018-03-25 20:01:45 +02:00
2018-05-05 23:06:59 +02:00
/* one last time with same name */
get_xsb_name ( buf , maxsize , target_subsong , xwb , streamFile , NULL ) ;
2017-08-12 11:46:28 +02:00
}