2008-12-18 20:04:35 +01:00
|
|
|
#include "meta.h"
|
2017-02-25 13:57:18 +01:00
|
|
|
#include "../coding/coding.h"
|
2018-05-05 23:06:59 +02:00
|
|
|
#include <string.h>
|
2019-03-23 15:50:59 +01:00
|
|
|
#include "xwb_xsb.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;
|
2018-10-07 02:23:05 +02:00
|
|
|
|
2019-03-23 15:50:59 +01:00
|
|
|
char wavebank_name[64+1];
|
|
|
|
|
2018-10-07 02:23:05 +02:00
|
|
|
int is_crackdown;
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
int fix_xma_num_samples;
|
|
|
|
int fix_xma_loop_samples;
|
2017-02-25 13:57:18 +01:00
|
|
|
} xwb_header;
|
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
static void get_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf);
|
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) */
|
2020-09-19 00:04:57 +02:00
|
|
|
VGMSTREAM* init_vgmstream_xwb(STREAMFILE* sf) {
|
|
|
|
VGMSTREAM* vgmstream = NULL;
|
2019-03-23 15:50:59 +01:00
|
|
|
off_t start_offset, offset, suboffset;
|
2018-03-08 22:51:50 +01:00
|
|
|
xwb_header xwb = {0};
|
2020-09-19 00:04:57 +02:00
|
|
|
int target_subsong = sf->stream_index;
|
|
|
|
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
|
|
|
|
int32_t (*read_s32)(off_t,STREAMFILE*) = NULL;
|
2017-02-25 13:57:18 +01:00
|
|
|
|
|
|
|
|
2018-03-08 22:51:50 +01:00
|
|
|
/* checks */
|
2018-08-25 12:21:21 +02:00
|
|
|
/* .xwb: standard
|
2019-01-01 19:04:39 +01:00
|
|
|
* .xna: Touhou Makukasai ~ Fantasy Danmaku Festival (PC)
|
2019-03-23 15:50:59 +01:00
|
|
|
* (extensionless): Ikaruga (X360/PC), Grabbed by the Ghoulies (Xbox) */
|
2020-09-19 00:04:57 +02:00
|
|
|
if (!check_extensions(sf,"xwb,xna,"))
|
2018-03-08 22:51:50 +01:00
|
|
|
goto fail;
|
2020-09-19 00:04:57 +02:00
|
|
|
if ((read_u32be(0x00,sf) != 0x57424E44) && /* "WBND" (LE) */
|
|
|
|
(read_u32be(0x00,sf) != 0x444E4257)) /* "DNBW" (BE) */
|
2010-01-10 18:29:19 +01:00
|
|
|
goto fail;
|
2008-12-18 20:04:35 +01:00
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.little_endian = read_u32be(0x00,sf) == 0x57424E44; /* WBND */
|
2017-02-25 13:57:18 +01:00
|
|
|
if (xwb.little_endian) {
|
2020-09-19 00:04:57 +02:00
|
|
|
read_u32 = read_u32le;
|
|
|
|
read_s32 = read_s32le;
|
2017-02-25 13:57:18 +01:00
|
|
|
} else {
|
2020-09-19 00:04:57 +02:00
|
|
|
read_u32 = read_u32be;
|
|
|
|
read_s32 = read_s32be;
|
2017-02-25 13:57:18 +01:00
|
|
|
}
|
2008-12-18 20:04:35 +01:00
|
|
|
|
2017-02-25 13:57:18 +01:00
|
|
|
|
|
|
|
/* read main header (WAVEBANKHEADER) */
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.version = read_u32(0x04, sf); /* XACT3: 0x04=tool version, 0x08=header version */
|
2008-12-18 20:04:35 +01:00
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
/* Crackdown 1 (X360), essentially XACT2 but may have split header in some cases, compact entries change */
|
2018-10-07 02:23:05 +02:00
|
|
|
if (xwb.version == XACT_CRACKDOWN) {
|
2017-05-18 22:14:32 +02:00
|
|
|
xwb.version = XACT2_2_MAX;
|
2018-10-07 02:23:05 +02:00
|
|
|
xwb.is_crackdown = 1;
|
|
|
|
}
|
2017-05-18 22:14:32 +02:00
|
|
|
|
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) {
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.total_subsongs = read_s32(0x0c, sf);
|
|
|
|
read_string(xwb.wavebank_name,0x10+1, 0x10, sf); /* null-terminated */
|
2018-03-25 20:01:45 +02:00
|
|
|
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;
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.data_size = get_streamfile_size(sf) - xwb.data_offset;
|
2018-03-25 20:01:45 +02:00
|
|
|
|
|
|
|
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 {
|
2019-03-23 15:50:59 +01:00
|
|
|
offset = xwb.version <= XACT2_2_MAX ? 0x08 : 0x0c;
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.base_offset = read_s32(offset+0x00, sf);//BANKDATA
|
|
|
|
xwb.base_size = read_s32(offset+0x04, sf);
|
|
|
|
xwb.entry_offset= read_s32(offset+0x08, sf);//ENTRYMETADATA
|
|
|
|
xwb.entry_size = read_s32(offset+0x0c, sf);
|
2018-03-25 20:01:45 +02:00
|
|
|
|
|
|
|
/* read extra segments (values can be 0 == no segment) */
|
|
|
|
if (xwb.version <= XACT1_1_MAX) {
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.names_offset = read_s32(offset+0x10, sf);//ENTRYNAMES
|
|
|
|
xwb.names_size = read_s32(offset+0x14, sf);
|
2018-03-25 20:01:45 +02:00
|
|
|
xwb.names_entry_size= 0x40;
|
|
|
|
xwb.extra_offset = 0;
|
|
|
|
xwb.extra_size = 0;
|
2019-03-23 15:50:59 +01:00
|
|
|
suboffset = 0x04*2;
|
2018-03-25 20:01:45 +02:00
|
|
|
}
|
|
|
|
else if (xwb.version <= XACT2_1_MAX) {
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.names_offset = read_s32(offset+0x10, sf);//ENTRYNAMES
|
|
|
|
xwb.names_size = read_s32(offset+0x14, sf);
|
2018-03-25 20:01:45 +02:00
|
|
|
xwb.names_entry_size= 0x40;
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.extra_offset = read_s32(offset+0x18, sf);//EXTRA
|
|
|
|
xwb.extra_size = read_s32(offset+0x1c, sf);
|
2019-03-23 15:50:59 +01:00
|
|
|
suboffset = 0x04*2 + 0x04*2;
|
2018-03-25 20:01:45 +02:00
|
|
|
} else {
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.extra_offset = read_s32(offset+0x10, sf);//SEEKTABLES
|
|
|
|
xwb.extra_size = read_s32(offset+0x14, sf);
|
|
|
|
xwb.names_offset = read_s32(offset+0x18, sf);//ENTRYNAMES
|
|
|
|
xwb.names_size = read_s32(offset+0x1c, sf);
|
2018-03-25 20:01:45 +02:00
|
|
|
xwb.names_entry_size= 0x40;
|
2019-03-23 15:50:59 +01:00
|
|
|
suboffset = 0x04*2 + 0x04*2;
|
2018-03-25 20:01:45 +02:00
|
|
|
}
|
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.data_offset = read_s32(offset+0x10+suboffset, sf);//ENTRYWAVEDATA
|
|
|
|
xwb.data_size = read_s32(offset+0x14+suboffset, sf);
|
2017-04-22 09:53:28 +02:00
|
|
|
|
|
|
|
/* for Techland's XWB with no data */
|
|
|
|
if (xwb.base_offset == 0) goto fail;
|
|
|
|
|
|
|
|
/* read base entry (WAVEBANKDATA) */
|
2019-03-23 15:50:59 +01:00
|
|
|
offset = xwb.base_offset;
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.base_flags = read_u32(offset+0x00, sf);
|
|
|
|
xwb.total_subsongs = read_s32(offset+0x04, sf);
|
|
|
|
read_string(xwb.wavebank_name,0x40+1, offset+0x08, sf); /* null-terminated */
|
2019-03-23 15:50:59 +01:00
|
|
|
suboffset = 0x08 + (xwb.version <= XACT1_1_MAX ? 0x10 : 0x40);
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.entry_elem_size = read_s32(offset+suboffset+0x00, sf);
|
2017-04-22 09:53:28 +02:00
|
|
|
/* suboff+0x04: meta name entry size */
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.entry_alignment = read_s32(offset+suboffset+0x08, sf); /* usually 1 dvd sector */
|
|
|
|
xwb.format = read_s32(offset+suboffset+0x0c, sf); /* compact mode only */
|
2017-04-22 09:53:28 +02:00
|
|
|
/* suboff+0x10: build time 64b (XACT2/3) */
|
|
|
|
}
|
2017-02-25 13:57:18 +01:00
|
|
|
|
2019-03-23 15:50:59 +01:00
|
|
|
//;VGM_LOG("XWB: wavebank name='%s'\n", xwb.wavebank_name);
|
|
|
|
|
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) */
|
2019-03-23 15:50:59 +01:00
|
|
|
offset = xwb.entry_offset + (target_subsong-1) * xwb.entry_elem_size;
|
2018-03-08 22:51:50 +01:00
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
|
|
|
|
if ((xwb.base_flags & WAVEBANK_FLAGS_COMPACT) && xwb.is_crackdown) {
|
|
|
|
/* mutant compact (w/ entry_elem_size=0x08) [Crackdown (X360)] */
|
|
|
|
uint32_t entry, size_sectors, sector_offset;
|
|
|
|
|
|
|
|
entry = read_u32(offset+0x00, sf);
|
|
|
|
size_sectors = ((entry >> 19) & 0x1FFF); /* 13b, exact size in sectors */
|
|
|
|
sector_offset = (entry & 0x7FFFF); /* 19b, offset within data in sectors */
|
|
|
|
xwb.stream_size = size_sectors * xwb.entry_alignment;
|
|
|
|
xwb.num_samples = read_u32(offset+0x04, sf);
|
|
|
|
|
|
|
|
xwb.stream_offset = xwb.data_offset + sector_offset * xwb.entry_alignment;
|
|
|
|
}
|
|
|
|
else if (xwb.base_flags & WAVEBANK_FLAGS_COMPACT) {
|
|
|
|
/* compact entry [NFL Fever 2004 demo from Amped 2 (Xbox)] */
|
2018-03-08 22:51:50 +01:00
|
|
|
uint32_t entry, size_deviation, sector_offset;
|
|
|
|
off_t next_stream_offset;
|
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
entry = read_u32(offset+0x00, sf);
|
2018-03-08 22:51:50 +01:00
|
|
|
size_deviation = ((entry >> 21) & 0x7FF); /* 11b, padding data for sector alignment in bytes*/
|
|
|
|
sector_offset = (entry & 0x1FFFFF); /* 21b, offset within data in sectors */
|
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.stream_offset = xwb.data_offset + sector_offset * xwb.entry_alignment;
|
2018-03-08 22:51:50 +01:00
|
|
|
|
|
|
|
/* find size using next offset */
|
|
|
|
if (target_subsong < xwb.total_subsongs) {
|
2020-09-19 00:04:57 +02:00
|
|
|
uint32_t next_entry = read_u32(offset + xwb.entry_elem_size, sf);
|
|
|
|
next_stream_offset = xwb.data_offset + (next_entry & 0x1FFFFF) * xwb.entry_alignment;
|
2018-03-08 22:51:50 +01:00
|
|
|
}
|
|
|
|
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) {
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.format = read_u32(offset+0x00, sf);
|
|
|
|
xwb.stream_offset = xwb.data_offset + read_u32(offset+0x04, sf);
|
|
|
|
xwb.stream_size = read_u32(offset+0x08, sf);
|
2017-04-22 09:53:28 +02:00
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.loop_start = read_u32(offset+0x0c, sf);
|
|
|
|
xwb.loop_end = read_u32(offset+0x10, sf);//length
|
2017-04-22 09:53:28 +02:00
|
|
|
}
|
2017-02-25 13:57:18 +01:00
|
|
|
else {
|
2020-09-19 00:04:57 +02:00
|
|
|
uint32_t entry_info = read_u32(offset+0x00, sf);
|
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
|
|
|
}
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.format = read_u32(offset+0x04, sf);
|
|
|
|
xwb.stream_offset = xwb.data_offset + read_u32(offset+0x08, sf);
|
|
|
|
xwb.stream_size = read_u32(offset+0x0c, sf);
|
2009-03-12 16:28:59 +01:00
|
|
|
|
2018-10-07 02:23:05 +02:00
|
|
|
if (xwb.version <= XACT2_1_MAX) { /* LoopRegion (bytes) */
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.loop_start = read_u32(offset+0x10, sf);
|
|
|
|
xwb.loop_end = read_u32(offset+0x14, sf);//length (LoopRegion) or offset (XMALoopRegion in late XACT2)
|
2017-04-22 09:53:28 +02:00
|
|
|
} else { /* LoopRegion (samples) */
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.loop_start_sample = read_u32(offset+0x10, sf);
|
|
|
|
xwb.loop_end_sample = read_u32(offset+0x14, sf) + 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
|
2020-09-26 19:20:40 +02:00
|
|
|
&& (xwb.bits_per_sample == 0x00 || xwb.bits_per_sample == 0x01) /* bps=0+ba=2 in mono? (Blossom Tales) */
|
|
|
|
&& (xwb.block_align == 0x02 || xwb.block_align == 0x04)
|
2020-09-19 00:04:57 +02:00
|
|
|
&& read_u32le(xwb.stream_offset + 0x08, sf) == xwb.sample_rate /* DSP header */
|
|
|
|
&& read_u16le(xwb.stream_offset + 0x0e, sf) == 0
|
|
|
|
&& read_u32le(xwb.stream_offset + 0x18, sf) == 2
|
2019-07-28 23:35:21 +02:00
|
|
|
/*&& xwb.data_size == 0x55951c1c*/) { /* some kind of id in Stardew Valley? */
|
|
|
|
/* Stardew Valley (Switch), Skulls of the Shogun (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);
|
|
|
|
|
2019-07-28 23:35:21 +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 */
|
2020-09-19 00:04:57 +02:00
|
|
|
if (xwb.data_offset + xwb.stream_size > get_streamfile_size(sf))
|
2017-11-10 19:31:54 +01:00
|
|
|
goto fail;
|
2020-09-19 00:04:57 +02:00
|
|
|
//if (xwb.data_offset + xwb.data_size > get_streamfile_size(sf)) /* badly split */
|
2019-06-23 22:25:55 +02:00
|
|
|
// goto fail;
|
2017-11-10 19:31:54 +01:00
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
else if (xwb.version <= XACT2_1_MAX && (xwb.codec == XMA1 || xwb.codec == XMA2) && xwb.loop_flag) {
|
2018-10-07 02:23:05 +02:00
|
|
|
/* v38: byte offset, v40+: sample offset, v39: ? */
|
|
|
|
/* need to manually find sample offsets, thanks to Microsoft's 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
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
xma_get_samples(&msd, sf);
|
2017-04-15 23:58:19 +02:00
|
|
|
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-10-07 02:23:05 +02:00
|
|
|
/* if provided, xwb.num_samples is equal to msd.num_samples after proper adjustments (+ 128 - start_skip - end_skip) */
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
xwb.fix_xma_loop_samples = 1;
|
|
|
|
xwb.fix_xma_num_samples = 0;
|
2017-03-05 17:34:37 +01:00
|
|
|
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
/* for XWB v22 (and below?) this seems normal [Project Gotham Racing (X360)] */
|
|
|
|
if (xwb.num_samples == 0) {
|
2020-09-19 00:04:57 +02:00
|
|
|
xwb.num_samples = msd.num_samples;
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
xwb.fix_xma_num_samples = 1;
|
|
|
|
}
|
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) {
|
2018-10-07 02:23:05 +02:00
|
|
|
/* unlike prev versions, xwb.num_samples is the full size without adjustments */
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
xwb.fix_xma_loop_samples = 1;
|
|
|
|
xwb.fix_xma_num_samples = 1;
|
2018-10-07 02:23:05 +02:00
|
|
|
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
/* Crackdown does use xwb.num_samples after adjustments (but not loops) */
|
2018-10-07 02:23:05 +02:00
|
|
|
if (xwb.is_crackdown) {
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
xwb.fix_xma_num_samples = 0;
|
2018-10-07 02:23:05 +02:00
|
|
|
}
|
2017-05-18 22:14:32 +02:00
|
|
|
}
|
2019-01-05 04:32:32 +01:00
|
|
|
|
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;
|
2020-09-19 00:04:57 +02:00
|
|
|
get_name(vgmstream->stream_name,STREAM_NAME_SIZE, target_subsong, &xwb, sf);
|
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;
|
2019-10-20 16:35:52 +02:00
|
|
|
vgmstream->frame_size = (xwb.block_align + 22) * xwb.channels; /*22=CONVERSION_OFFSET (?)*/
|
2017-02-25 13:57:18 +01:00
|
|
|
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) */
|
2018-10-07 02:23:05 +02:00
|
|
|
uint8_t buf[0x100];
|
2017-02-25 13:57:18 +01:00
|
|
|
int bytes;
|
2009-03-12 16:28:59 +01:00
|
|
|
|
2018-10-07 23:28:01 +02:00
|
|
|
bytes = ffmpeg_make_riff_xma1(buf,0x100, vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, 0);
|
2020-09-19 00:04:57 +02:00
|
|
|
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, xwb.stream_offset,xwb.stream_size);
|
2018-01-27 10:57:29 +01:00
|
|
|
if (!vgmstream->codec_data) goto fail;
|
2017-02-25 13:57:18 +01:00
|
|
|
vgmstream->coding_type = coding_FFmpeg;
|
|
|
|
vgmstream->layout_type = layout_none;
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
xma_fix_raw_samples(vgmstream, sf, xwb.stream_offset,xwb.stream_size, 0, xwb.fix_xma_num_samples,xwb.fix_xma_loop_samples);
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
|
|
|
|
/* this fixes some XMA1, perhaps the above isn't reading end_skip correctly (doesn't happen for all files though) */
|
|
|
|
if (vgmstream->loop_flag &&
|
|
|
|
vgmstream->loop_end_sample > vgmstream->num_samples) {
|
|
|
|
VGM_LOG("XWB: fix XMA1 looping\n");
|
|
|
|
vgmstream->loop_end_sample = vgmstream->num_samples;
|
|
|
|
}
|
2017-02-25 13:57:18 +01:00
|
|
|
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) */
|
2018-10-07 02:23:05 +02:00
|
|
|
uint8_t buf[0x100];
|
2017-02-25 13:57:18 +01:00
|
|
|
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);
|
|
|
|
|
2018-10-07 23:28:01 +02:00
|
|
|
bytes = ffmpeg_make_riff_xma2(buf,0x100, vgmstream->num_samples, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size);
|
2020-09-19 00:04:57 +02:00
|
|
|
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, xwb.stream_offset,xwb.stream_size);
|
2018-01-27 10:57:29 +01:00
|
|
|
if (!vgmstream->codec_data) goto fail;
|
2017-02-25 13:57:18 +01:00
|
|
|
vgmstream->coding_type = coding_FFmpeg;
|
|
|
|
vgmstream->layout_type = layout_none;
|
Fix XMA gapless/looping/samples
fixes: standard, wem, xwc, xwb, xnb, xwx, rak, pk, txth, genh, seg, rsd, past, p3d, nub-xma, gtd, gsp, fsb, eaac, cxs, awc, aac
2018-11-18 17:01:31 +01:00
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
xma_fix_raw_samples(vgmstream, sf, xwb.stream_offset,xwb.stream_size, 0, xwb.fix_xma_num_samples,xwb.fix_xma_loop_samples);
|
2017-02-25 13:57:18 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-10-07 02:23:05 +02:00
|
|
|
case WMA: { /* WMAudio1 (WMA v2): Prince of Persia 2 port (Xbox) */
|
2017-02-25 13:57:18 +01:00
|
|
|
ffmpeg_codec_data *ffmpeg_data = NULL;
|
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
ffmpeg_data = init_ffmpeg_offset(sf, xwb.stream_offset,xwb.stream_size);
|
2017-02-25 13:57:18 +01:00
|
|
|
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 */
|
2018-10-07 02:23:05 +02:00
|
|
|
uint8_t buf[0x100];
|
2017-02-25 13:57:18 +01:00
|
|
|
int bytes, bps_index, block_align, block_index, avg_bps, wma_codec;
|
|
|
|
|
2018-10-07 02:23:05 +02:00
|
|
|
bps_index = (xwb.block_align >> 5); /* upper 3b bytes-per-second index (docs say 2b+6b but are wrong) */
|
2017-02-25 13:57:18 +01:00
|
|
|
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 */
|
|
|
|
|
2018-10-07 23:28:01 +02:00
|
|
|
bytes = ffmpeg_make_riff_xwma(buf,0x100, wma_codec, xwb.stream_size, vgmstream->channels, vgmstream->sample_rate, avg_bps, block_align);
|
2020-09-19 00:04:57 +02:00
|
|
|
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, xwb.stream_offset,xwb.stream_size);
|
2018-01-27 10:57:29 +01:00
|
|
|
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)] */
|
2019-08-26 22:58:43 +02:00
|
|
|
int block_align, encoder_delay;
|
2017-03-18 00:22:20 +01:00
|
|
|
|
2019-08-26 22:58:43 +02:00
|
|
|
block_align = xwb.block_align * vgmstream->channels;
|
|
|
|
encoder_delay = 1024; /* assumed */
|
|
|
|
vgmstream->num_samples -= encoder_delay;
|
2017-03-18 00:22:20 +01:00
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
vgmstream->codec_data = init_ffmpeg_atrac3_raw(sf, xwb.stream_offset,xwb.stream_size, vgmstream->num_samples,vgmstream->channels,vgmstream->sample_rate, block_align, encoder_delay);
|
2019-08-26 22:58:43 +02:00
|
|
|
if (!vgmstream->codec_data) goto fail;
|
2017-03-18 00:22:20 +01:00
|
|
|
vgmstream->coding_type = coding_FFmpeg;
|
|
|
|
vgmstream->layout_type = layout_none;
|
|
|
|
break;
|
|
|
|
}
|
2019-10-20 19:50:35 +02:00
|
|
|
#endif
|
|
|
|
#ifdef VGM_USE_VORBIS
|
2018-05-05 23:06:59 +02:00
|
|
|
case OGG: { /* Oddworld: Strangers Wrath (iOS/Android) extension */
|
2020-09-19 00:04:57 +02:00
|
|
|
vgmstream->codec_data = init_ogg_vorbis(sf, xwb.stream_offset, xwb.stream_size, NULL);
|
2019-10-20 19:50:35 +02:00
|
|
|
if (!vgmstream->codec_data) goto fail;
|
|
|
|
vgmstream->coding_type = coding_OGG_VORBIS;
|
2017-11-10 19:31:54 +01:00
|
|
|
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;
|
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
dsp_read_coefs(vgmstream,sf,xwb.stream_offset + 0x1c,vgmstream->interleave_block_size,!xwb.little_endian);
|
|
|
|
dsp_read_hist (vgmstream,sf,xwb.stream_offset + 0x3c,vgmstream->interleave_block_size,!xwb.little_endian);
|
2018-05-05 23:06:59 +02:00
|
|
|
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;
|
2020-09-19 00:04:57 +02:00
|
|
|
STREAMFILE* temp_sf = NULL;
|
2018-05-27 17:30:59 +02:00
|
|
|
|
|
|
|
/* standard RIFF, use subfile (seems doesn't use xwb loops) */
|
|
|
|
VGM_ASSERT(xwb.loop_flag, "XWB: RIFF ATRAC9 loop flag found\n");
|
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
temp_sf = setup_subfile_streamfile(sf, xwb.stream_offset,xwb.stream_size, "at9");
|
|
|
|
if (!temp_sf) goto fail;
|
2018-05-27 17:30:59 +02:00
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
temp_vgmstream = init_vgmstream_riff(temp_sf);
|
|
|
|
close_streamfile(temp_sf);
|
2018-05-27 17:30:59 +02:00
|
|
|
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;
|
2019-03-23 15:50:59 +01:00
|
|
|
strcpy(temp_vgmstream->stream_name, vgmstream->stream_name);
|
2018-05-27 17:30:59 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
if ( !vgmstream_open_stream(vgmstream,sf,start_offset) )
|
2017-02-25 13:57:18 +01:00
|
|
|
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
|
|
|
/* ****************************************************************************** */
|
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
static int get_xwb_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf) {
|
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;
|
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
read = read_string(buf,xwb->names_entry_size, xwb->names_offset + xwb->names_entry_size*(target_subsong-1),sf);
|
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;
|
|
|
|
}
|
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
static int get_xsb_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf) {
|
2018-03-25 20:01:45 +02:00
|
|
|
xsb_header xsb = {0};
|
2017-08-12 11:46:28 +02:00
|
|
|
|
2019-03-23 15:50:59 +01:00
|
|
|
xsb.selected_stream = target_subsong - 1;
|
2020-09-19 00:04:57 +02:00
|
|
|
if (!parse_xsb(&xsb, sf, xwb->wavebank_name))
|
2017-08-12 11:46:28 +02:00
|
|
|
goto fail;
|
|
|
|
|
2019-03-23 15:50:59 +01:00
|
|
|
if ((xwb->version <= XACT1_1_MAX && xsb.version > XSB_XACT1_2_MAX) ||
|
|
|
|
(xwb->version <= XACT2_2_MAX && xsb.version > XSB_XACT2_MAX)) {
|
|
|
|
VGM_LOG("XSB: mismatched XACT versions: xsb v%i vs xwb v%i\n", xsb.version, xwb->version);
|
2017-08-12 11:46:28 +02:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2019-03-23 15:50:59 +01:00
|
|
|
//;VGM_LOG("XSB: name found=%i at %lx\n", xsb.parse_found, xsb.name_offset);
|
2019-08-10 18:11:30 +02:00
|
|
|
if (!xsb.name_len || xsb.name[0] == '\0')
|
2017-08-12 11:46:28 +02:00
|
|
|
goto fail;
|
|
|
|
|
2019-08-10 17:26:44 +02:00
|
|
|
strncpy(buf,xsb.name,maxsize);
|
|
|
|
buf[maxsize-1] = '\0';
|
2019-03-23 15:50:59 +01:00
|
|
|
return 1;
|
2017-08-12 11:46:28 +02:00
|
|
|
fail:
|
2019-03-23 15:50:59 +01:00
|
|
|
return 0;
|
2018-03-25 20:01:45 +02:00
|
|
|
}
|
|
|
|
|
2019-10-14 00:29:40 +02:00
|
|
|
static int get_wbh_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf) {
|
|
|
|
int selected_stream = target_subsong - 1;
|
|
|
|
int version, name_count;
|
|
|
|
off_t offset, name_number;
|
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
if (read_u32be(0x00, sf) != 0x57424844) /* "WBHD" */
|
2019-10-14 00:29:40 +02:00
|
|
|
goto fail;
|
2020-09-19 00:04:57 +02:00
|
|
|
version = read_u32le(0x04, sf);
|
2019-10-14 00:29:40 +02:00
|
|
|
if (version != 1)
|
|
|
|
goto fail;
|
2020-09-19 00:04:57 +02:00
|
|
|
name_count = read_u32le(0x08, sf);
|
2019-10-14 00:29:40 +02:00
|
|
|
|
|
|
|
if (selected_stream > name_count)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/* next table:
|
|
|
|
* - 0x00: wave id? (ordered from 0 to N)
|
|
|
|
* - 0x04: always 0 */
|
|
|
|
offset = 0x10 + 0x08 * name_count;
|
|
|
|
|
|
|
|
name_number = 0;
|
|
|
|
while (offset < get_streamfile_size(sf)) {
|
|
|
|
size_t name_len = read_string(buf, maxsize, offset, sf) + 1;
|
|
|
|
|
|
|
|
if (name_len == 0)
|
|
|
|
goto fail;
|
|
|
|
if (name_number == selected_stream)
|
|
|
|
break;
|
|
|
|
|
|
|
|
name_number++;
|
|
|
|
offset += name_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
fail:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-09-19 00:04:57 +02:00
|
|
|
static void get_name(char* buf, size_t maxsize, int target_subsong, xwb_header* xwb, STREAMFILE* sf_xwb) {
|
|
|
|
STREAMFILE* sf_name = NULL;
|
2018-03-25 20:01:45 +02:00
|
|
|
int name_found;
|
|
|
|
|
2019-03-23 15:50:59 +01:00
|
|
|
/* try to get the stream name in the .xwb, though they are very rarely included */
|
2020-09-19 00:04:57 +02:00
|
|
|
name_found = get_xwb_name(buf, maxsize, target_subsong, xwb, sf_xwb);
|
2018-03-25 20:01:45 +02:00
|
|
|
if (name_found) return;
|
|
|
|
|
2019-10-14 00:29:40 +02:00
|
|
|
/* try again in a companion files */
|
|
|
|
|
|
|
|
if (xwb->version == 1) {
|
|
|
|
/* .wbh, a simple name container */
|
2020-09-19 00:04:57 +02:00
|
|
|
sf_name = open_streamfile_by_ext(sf_xwb, "wbh");
|
2019-10-14 00:29:40 +02:00
|
|
|
if (!sf_name) return; /* rarely found [Pac-Man World 2 (Xbox)] */
|
|
|
|
|
|
|
|
name_found = get_wbh_name(buf, maxsize, target_subsong, xwb, sf_name);
|
|
|
|
close_streamfile(sf_name);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* .xsb, a comically complex cue format */
|
2020-09-19 00:04:57 +02:00
|
|
|
sf_name = open_xsb_filename_pair(sf_xwb);
|
2019-10-14 00:29:40 +02:00
|
|
|
if (!sf_name) return; /* not all xwb have xsb though */
|
|
|
|
|
|
|
|
name_found = get_xsb_name(buf, maxsize, target_subsong, xwb, sf_name);
|
|
|
|
close_streamfile(sf_name);
|
|
|
|
}
|
2018-05-05 23:06:59 +02:00
|
|
|
|
|
|
|
|
2019-03-23 15:50:59 +01:00
|
|
|
if (!name_found) {
|
|
|
|
buf[0] = '\0';
|
2018-05-05 23:06:59 +02:00
|
|
|
}
|
2017-08-12 11:46:28 +02:00
|
|
|
}
|