mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-15 11:07:40 +01:00
commit
07f6d1dbef
@ -246,6 +246,14 @@ VGMSTREAM_DECLARE_FILE_TYPE("S14", s14);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SAB", sab);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SAD", sad);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SAP", sap);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SB0", sb0);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SB1", sb1);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SB2", sb2);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SB3", sb3);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SB4", sb4);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SB5", sb5);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SB6", sb6);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SB7", sb7);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SC", sc);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SCD", scd);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SCK", sck);
|
||||
|
@ -32,8 +32,10 @@ void decode_fsb_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * o
|
||||
void decode_wwise_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
void decode_ref_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel);
|
||||
void decode_awc_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels);
|
||||
size_t ima_bytes_to_samples(size_t bytes, int channels);
|
||||
size_t ubi_ima_bytes_to_samples(size_t bytes, int channels, STREAMFILE *streamFile, off_t offset);
|
||||
|
||||
/* ngc_dsp_decoder */
|
||||
void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
|
@ -41,21 +41,24 @@ static const int IMA_IndexTable[16] =
|
||||
};
|
||||
|
||||
|
||||
/* Original IMA */
|
||||
/* Alt IMA */
|
||||
static void ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) {
|
||||
int sample_nibble, sample_decoded, step, delta;
|
||||
|
||||
//"original" ima nibble expansion
|
||||
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
|
||||
sample_decoded = *hist1 << 3;
|
||||
sample_decoded = *hist1;
|
||||
|
||||
sample_decoded = sample_decoded << 3;
|
||||
step = ADPCMTable[*step_index];
|
||||
delta = step * (sample_nibble & 7) * 2 + step;
|
||||
if (sample_nibble & 8)
|
||||
sample_decoded -= delta;
|
||||
else
|
||||
sample_decoded += delta;
|
||||
sample_decoded = sample_decoded >> 3;
|
||||
|
||||
*hist1 = clamp16(sample_decoded >> 3);
|
||||
*hist1 = clamp16(sample_decoded);
|
||||
*step_index += IMA_IndexTable[sample_nibble];
|
||||
if (*step_index < 0) *step_index=0;
|
||||
if (*step_index > 88) *step_index=88;
|
||||
@ -117,7 +120,8 @@ static void snds_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset,
|
||||
|
||||
step = ADPCMTable[*step_index];
|
||||
delta = (sample_nibble & 7) * step / 4 + step / 8;
|
||||
if (sample_nibble & 8) delta = -delta;
|
||||
if (sample_nibble & 8)
|
||||
delta = -delta;
|
||||
sample_decoded = *hist1 + delta;
|
||||
|
||||
*hist1 = clamp16(sample_decoded);
|
||||
@ -146,6 +150,25 @@ static void otns_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset,
|
||||
if (*step_index > 88) *step_index=88;
|
||||
}
|
||||
|
||||
/* algorithm by Zench (https://bitbucket.org/Zenchreal/decubisnd) */
|
||||
static void ubi_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) {
|
||||
int sample_nibble, sample_decoded, step, delta;
|
||||
|
||||
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
|
||||
|
||||
step = ADPCMTable[*step_index];
|
||||
*step_index += IMA_IndexTable[sample_nibble];
|
||||
if (*step_index < 0) *step_index=0;
|
||||
if (*step_index > 88) *step_index=88;
|
||||
|
||||
delta = (((sample_nibble & 7) * 2 + 1) * step) >> 3;
|
||||
if (sample_nibble & 8)
|
||||
delta = -delta;
|
||||
sample_decoded = *hist1 + delta;
|
||||
|
||||
*hist1 = clamp16(sample_decoded);
|
||||
}
|
||||
|
||||
|
||||
void decode_nds_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
int i, sample_count;
|
||||
@ -711,6 +734,67 @@ void decode_awc_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci
|
||||
}
|
||||
|
||||
|
||||
/* DVI stereo/mono with some mini header and sample output */
|
||||
void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
int i, sample_count = 0;
|
||||
|
||||
int32_t hist1 = stream->adpcm_history1_32;
|
||||
int step_index = stream->adpcm_step_index;
|
||||
|
||||
//internal interleave
|
||||
|
||||
//header in the beginning of the stream
|
||||
if (stream->channel_start_offset == stream->offset) {
|
||||
int version, big_endian, header_samples, max_samples_to_do;
|
||||
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
|
||||
off_t offset = stream->offset;
|
||||
|
||||
/* header fields mostly unknown (vary a lot or look like flags),
|
||||
* 0x07 0x06 = major/minor tool version?, 0x0c: stereo flag? */
|
||||
version = read_8bit(offset + 0x00, stream->streamfile);
|
||||
big_endian = version < 5; //todo and sb.big_endian?
|
||||
read_16bit = big_endian ? read_16bitBE : read_16bitLE;
|
||||
|
||||
header_samples = read_16bit(offset + 0x0E, stream->streamfile); /* always 10 (per channel) */
|
||||
hist1 = read_16bit(offset + 0x10 + channel*0x04,stream->streamfile);
|
||||
step_index = read_8bit(offset + 0x12 + channel*0x04,stream->streamfile);
|
||||
offset += 0x10 + 0x08 + 0x04; //todo v6 has extra 0x08?
|
||||
|
||||
/* write PCM samples, must be written to match header's num_samples (hist mustn't) */
|
||||
max_samples_to_do = ((samples_to_do > header_samples) ? header_samples : samples_to_do);
|
||||
for (i = first_sample; i < max_samples_to_do; i++, sample_count += channelspacing) {
|
||||
outbuf[sample_count] = read_16bit(offset + channel*sizeof(sample) + i*channelspacing*sizeof(sample),stream->streamfile);
|
||||
first_sample++;
|
||||
samples_to_do--;
|
||||
}
|
||||
|
||||
/* header done */
|
||||
if (i == header_samples) {
|
||||
stream->offset = offset + header_samples*channelspacing*sizeof(sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
first_sample -= 10; //todo fix hack (needed to adjust nibble offset below)
|
||||
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++, sample_count += channelspacing) {
|
||||
off_t byte_offset = channelspacing == 1 ?
|
||||
stream->offset + i/2 : /* mono mode */
|
||||
stream->offset + i; /* stereo mode */
|
||||
int nibble_shift = channelspacing == 1 ?
|
||||
(!(i%2) ? 4:0) : /* mono mode (high first) */
|
||||
(channel==0 ? 4:0); /* stereo mode (high=L,low=R) */
|
||||
|
||||
ubi_ima_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index);
|
||||
outbuf[sample_count] = (short)(hist1); /* all samples are written */
|
||||
}
|
||||
|
||||
//external interleave
|
||||
|
||||
stream->adpcm_history1_32 = hist1;
|
||||
stream->adpcm_step_index = step_index;
|
||||
}
|
||||
|
||||
|
||||
size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels) {
|
||||
/* MS IMA blocks have a 4 byte header per channel; 2 samples per byte (2 nibbles) */
|
||||
@ -721,3 +805,19 @@ size_t ima_bytes_to_samples(size_t bytes, int channels) {
|
||||
/* 2 samples per byte (2 nibbles) in stereo or mono config */
|
||||
return bytes / channels * 2;
|
||||
}
|
||||
|
||||
size_t ubi_ima_bytes_to_samples(size_t bytes, int channels, STREAMFILE *streamFile, off_t offset) {
|
||||
int version, big_endian, header_samples;
|
||||
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
|
||||
size_t header_size = 0;
|
||||
|
||||
version = read_8bit(offset + 0x00, streamFile);
|
||||
big_endian = version < 5; //todo and sb.big_endian?
|
||||
read_16bit = big_endian ? read_16bitBE : read_16bitLE;
|
||||
|
||||
header_samples = read_16bit(offset + 0x0E, streamFile); /* always 10 (per channel) */
|
||||
header_size += 0x10 + 0x04 * channels + 0x04; //todo v6 has extra 0x08?
|
||||
header_size += header_samples * channels * sizeof(sample);
|
||||
|
||||
return header_samples + ima_bytes_to_samples(bytes - header_size, channels);
|
||||
}
|
||||
|
@ -38,7 +38,8 @@ int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, m
|
||||
{
|
||||
int current_data_size = info.frame_size;
|
||||
int current_padding = 0;
|
||||
if (info.layer == 3 && data->config.fsb_padding) { /* FSB padding for Layer III */
|
||||
/* FSB padding for Layer III or multichannel Layer II */
|
||||
if ((info.layer == 3 && data->config.fsb_padding) || data->config.fsb_padding == 16) {
|
||||
current_padding = (current_data_size % data->config.fsb_padding)
|
||||
? data->config.fsb_padding - (current_data_size % data->config.fsb_padding)
|
||||
: 0;
|
||||
@ -109,9 +110,9 @@ int mpeg_custom_parse_frame_default(VGMSTREAMCHANNEL *stream, mpeg_codec_data *d
|
||||
goto fail;
|
||||
current_data_size = info.frame_size;
|
||||
|
||||
/* get FSB padding for Layer III (Layer II doesn't use it, and Layer I doesn't seem to be supported) */
|
||||
/* Padding sometimes contains garbage like the next frame header so we can't feed it to mpg123 or it gets confused. */
|
||||
if (info.layer == 3 && data->config.fsb_padding) {
|
||||
/* get FSB padding for Layer III or multichannel Layer II (Layer I doesn't seem to be supported)
|
||||
* Padding sometimes contains garbage like the next frame header so we can't feed it to mpg123 or it gets confused. */
|
||||
if ((info.layer == 3 && data->config.fsb_padding) || data->config.fsb_padding == 16) {
|
||||
current_padding = (current_data_size % data->config.fsb_padding)
|
||||
? data->config.fsb_padding - (current_data_size % data->config.fsb_padding)
|
||||
: 0;
|
||||
|
@ -240,6 +240,14 @@ static const char* extension_list[] = {
|
||||
"sab",
|
||||
"sad",
|
||||
"sap",
|
||||
"sb0",
|
||||
"sb1",
|
||||
"sb2",
|
||||
"sb3",
|
||||
"sb4",
|
||||
"sb5",
|
||||
"sb6",
|
||||
"sb7",
|
||||
"sc",
|
||||
"scd",
|
||||
"sck",
|
||||
@ -457,6 +465,7 @@ static const coding_info coding_info_list[] = {
|
||||
{coding_WWISE_IMA, "Audiokinetic Wwise 4-bit IMA ADPCM"},
|
||||
{coding_REF_IMA, "Reflections 4-bit IMA ADPCM"},
|
||||
{coding_AWC_IMA, "Rockstar AWC 4-bit IMA ADPCM"},
|
||||
{coding_UBI_IMA, "Ubisoft 4-bit IMA ADPCM"},
|
||||
{coding_WS, "Westwood Studios VBR ADPCM"},
|
||||
{coding_ACM, "InterPlay ACM"},
|
||||
{coding_NWA0, "NWA DPCM Level 0"},
|
||||
@ -871,7 +880,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_NUB_XMA, "Namco NUB XMA header"},
|
||||
{meta_X360_PASX, "Namco PASX header"},
|
||||
{meta_XMA_RIFF, "Microsoft XMA RIFF header"},
|
||||
{meta_X360_AST, "Capcom AST header"},
|
||||
{meta_X360_AST, "Capcom AST (X360) header"},
|
||||
{meta_WWISE_RIFF, "Audiokinetic Wwise RIFF header"},
|
||||
{meta_UBI_RAKI, "Ubisoft RAKI header"},
|
||||
{meta_SXD, "Sony SXD header"},
|
||||
@ -892,9 +901,10 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_BINK, "RAD Game Tools Bink header"},
|
||||
{meta_EA_SNU, "Electronic Arts SNU header"},
|
||||
{meta_AWC, "Rockstar AWC header"},
|
||||
{meta_NSW_OPUS, ".OPUS header"},
|
||||
{meta_NSW_OPUS, "Nintendo Switch OPUS header"},
|
||||
{meta_PC_AL2, "Illwinter Game Design AL2 raw header"},
|
||||
{meta_PC_AST, "Capcom AST header"},
|
||||
{meta_PC_AST, "Capcom AST (PC) header"},
|
||||
{meta_UBI_SB, "Ubisoft SBx header"},
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
{meta_OGG_VORBIS, "Ogg Vorbis"},
|
||||
|
@ -1173,6 +1173,10 @@
|
||||
<File
|
||||
RelativePath=".\meta\ubi_raki.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\ubi_sb.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\vgs.c"
|
||||
|
@ -400,6 +400,7 @@
|
||||
<ClCompile Include="meta\vgs.c" />
|
||||
<ClCompile Include="meta\ubi_ckd.c" />
|
||||
<ClCompile Include="meta\ubi_raki.c" />
|
||||
<ClCompile Include="meta\ubi_sb.c" />
|
||||
<ClCompile Include="meta\vs.c" />
|
||||
<ClCompile Include="meta\vsf.c" />
|
||||
<ClCompile Include="meta\waa_wac_wad_wam.c" />
|
||||
|
@ -721,6 +721,9 @@
|
||||
<ClCompile Include="meta\ubi_raki.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\ubi_sb.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\vs.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
@ -688,4 +688,6 @@ VGMSTREAM * init_vgmstream_pc_al2(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_pc_ast(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE * streamFile);
|
||||
|
||||
#endif /*_META_H*/
|
||||
|
@ -2,30 +2,61 @@
|
||||
#include "../util.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* .OPUS - from Lego City Undercover (Switch) */
|
||||
/* .OPUS - from Switch games (Lego City Undercover, Ultra SF II, Disgaea 5) */
|
||||
VGMSTREAM * init_vgmstream_nsw_opus(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag = 0, channel_count;
|
||||
int num_samples = 0, loop_start = 0, loop_end = 0;
|
||||
off_t offset = 0;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
if ( !check_extensions(streamFile,"opus")) /* no relation to Ogg Opus */
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) != 0x01000080)
|
||||
/* variations, maybe custom */
|
||||
if (read_32bitBE(0x00,streamFile) == 0x01000080) { /* Lego City Undercover */
|
||||
offset = 0x00;
|
||||
}
|
||||
else if ((read_32bitBE(0x04,streamFile) == 0x00000000 && read_32bitBE(0x0c,streamFile) == 0x00000000) ||
|
||||
(read_32bitBE(0x04,streamFile) == 0xFFFFFFFF && read_32bitBE(0x0c,streamFile) == 0xFFFFFFFF)) { /* Disgaea 5 */
|
||||
offset = 0x10;
|
||||
|
||||
loop_start = read_32bitLE(0x00,streamFile);
|
||||
loop_end = read_32bitLE(0x08,streamFile);
|
||||
}
|
||||
else if (read_32bitLE(0x04,streamFile) == 0x02) { /* Ultra Street Fighter II */
|
||||
offset = read_32bitLE(0x1c,streamFile);
|
||||
|
||||
num_samples = read_32bitLE(0x00,streamFile);
|
||||
loop_start = read_32bitLE(0x08,streamFile);
|
||||
loop_end = read_32bitLE(0x0c,streamFile);
|
||||
}
|
||||
else {
|
||||
offset = 0x00;
|
||||
}
|
||||
|
||||
if (read_32bitBE(offset + 0x00,streamFile) != 0x01000080)
|
||||
goto fail;
|
||||
|
||||
start_offset = 0x28;
|
||||
channel_count = read_8bit(0x09,streamFile); /* assumed */
|
||||
/* other values in the header: no idea */
|
||||
start_offset = offset + 0x28;
|
||||
channel_count = read_8bit(offset + 0x09,streamFile); /* assumed */
|
||||
/* 0x0a: packet size if CBR?, other values: no idea */
|
||||
|
||||
loop_flag = (loop_end > 0); /* -1 when not set */
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = read_32bitLE(0x0c,streamFile);
|
||||
vgmstream->sample_rate = read_32bitLE(offset + 0x0c,streamFile);
|
||||
vgmstream->meta_type = meta_NSW_OPUS;
|
||||
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
uint8_t buf[0x100];
|
||||
@ -47,6 +78,7 @@ VGMSTREAM * init_vgmstream_nsw_opus(STREAMFILE *streamFile) {
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
if (vgmstream->num_samples == 0)
|
||||
vgmstream->num_samples = switch_opus_get_samples(start_offset, data_size, vgmstream->sample_rate, streamFile);
|
||||
}
|
||||
#else
|
||||
|
711
src/meta/ubi_sb.c
Normal file
711
src/meta/ubi_sb.c
Normal file
@ -0,0 +1,711 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
typedef enum { UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, FMT_OGG } ubi_sb_codec;
|
||||
typedef struct {
|
||||
int big_endian;
|
||||
int total_streams;
|
||||
int is_external;
|
||||
int autodetect_external;
|
||||
ubi_sb_codec codec;
|
||||
|
||||
/* main/fixed info */
|
||||
uint32_t version;
|
||||
size_t section1_num;
|
||||
size_t section2_num;
|
||||
size_t section3_num;
|
||||
size_t extra_size;
|
||||
int flag1;
|
||||
int flag2;
|
||||
|
||||
/* stream info config (format varies slightly per game) */
|
||||
size_t section1_entry_size;
|
||||
size_t section2_entry_size;
|
||||
size_t section3_entry_size;
|
||||
off_t external_flag_offset;
|
||||
off_t num_samples_offset;
|
||||
off_t sample_rate_offset;
|
||||
off_t channels_offset;
|
||||
off_t stream_type_offset;
|
||||
off_t stream_name_offset;
|
||||
off_t extra_name_offset;
|
||||
size_t stream_name_size;
|
||||
size_t stream_id_offset;
|
||||
int has_short_channels;
|
||||
int has_internal_names;
|
||||
|
||||
/* derived */
|
||||
size_t main_size;
|
||||
size_t section1_size;
|
||||
size_t section2_size;
|
||||
size_t section3_size;
|
||||
|
||||
/* stream info */
|
||||
uint32_t header_id;
|
||||
uint32_t header_type;
|
||||
size_t stream_size;
|
||||
off_t extra_offset;
|
||||
off_t stream_offset;
|
||||
uint32_t stream_id;
|
||||
|
||||
int stream_samples; /* usually only for external resources */
|
||||
int sample_rate;
|
||||
int channels;
|
||||
uint32_t stream_type;
|
||||
char stream_name[255];
|
||||
|
||||
|
||||
} ubi_sb_header;
|
||||
|
||||
static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile);
|
||||
static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile);
|
||||
|
||||
|
||||
/* .SBx - banks from Ubisoft's sound engine ("DARE" / "UbiSound Driver") games in ~2000-2008 */
|
||||
VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE *streamData = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag = 0;
|
||||
ubi_sb_header sb = {0};
|
||||
|
||||
/* check extension (number represents the platform, see later) */
|
||||
if ( !check_extensions(streamFile,"sb0,sb1,sb2,sb3,sb4,sb5,sb6,sb7") )
|
||||
goto fail;
|
||||
//memset(&sb,0,sizeof(ubi_sb_header)); //todo test for VC2010's init = {0} above (should be C89 but...)
|
||||
if (sb.flag1 != 0 || sb.codec!=0 || sb.stream_samples != 0) { VGM_LOG("UBI SB: not init ok\n"); goto fail; }
|
||||
|
||||
|
||||
/* .sb0 (sound bank) is a small multisong format (loaded in memory?) that contains SFX data
|
||||
* but can also reference .ss0/ls0 (sound stream) external files for longer streams.
|
||||
* A companion .sp0 (sound project) describes files and if it uses BANKs (.sb0) or MAPs (.sm0). */
|
||||
|
||||
/* main parse */
|
||||
if ( !parse_sb_header(&sb, streamFile) )
|
||||
goto fail;
|
||||
|
||||
|
||||
/* open external stream if needed */
|
||||
if (sb.autodetect_external) { /* works most of the time but could give false positives */
|
||||
VGM_LOG("UBI SB: autodetecting external stream '%s'\n", sb.stream_name);
|
||||
|
||||
streamData = open_stream_name(streamFile,sb.stream_name);
|
||||
if (!streamData) {
|
||||
streamData = streamFile; /* assume internal */
|
||||
if (sb.stream_size > get_streamfile_size(streamData)) {
|
||||
VGM_LOG("UBI SB: expected external stream\n");
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
sb.is_external = 1;
|
||||
}
|
||||
}
|
||||
else if (sb.is_external) {
|
||||
streamData = open_stream_name(streamFile,sb.stream_name);
|
||||
if (!streamData) {
|
||||
VGM_LOG("UBI SB: external stream '%s' not found\n", sb.stream_name);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else {
|
||||
streamData = streamFile;
|
||||
}
|
||||
|
||||
/* final offset */
|
||||
if (sb.is_external) {
|
||||
start_offset = sb.stream_offset;
|
||||
} else {
|
||||
start_offset = sb.main_size + sb.section1_size + sb.section2_size + sb.extra_size + sb.section3_size;
|
||||
start_offset += sb.stream_offset;
|
||||
}
|
||||
;VGM_LOG("so=%lx, main=%x, s1=%x, s2=%x, ex=%x, s3=%x, so=%lx\n", start_offset, sb.main_size, sb.section1_size, sb.section2_size, sb.extra_size, sb.section3_size, sb.stream_offset);
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(sb.channels,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sb.sample_rate;
|
||||
vgmstream->num_streams = sb.total_streams;
|
||||
vgmstream->meta_type = meta_UBI_SB;
|
||||
|
||||
|
||||
switch(sb.codec) {
|
||||
case UBI_ADPCM: { //todo move to its own decoder
|
||||
vgmstream->coding_type = coding_UBI_IMA;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->num_samples = ubi_ima_bytes_to_samples(sb.stream_size, sb.channels, streamData, start_offset);
|
||||
break;
|
||||
}
|
||||
|
||||
case RAW_PCM:
|
||||
vgmstream->coding_type = coding_PCM16LE;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x02;
|
||||
vgmstream->num_samples = pcm_bytes_to_samples(sb.stream_size, sb.channels, 16);
|
||||
if (sb.channels > 1) { VGM_LOG("UBI SB: >1 channel\n"); goto fail; } //todo
|
||||
break;
|
||||
|
||||
case RAW_PSX:
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = sb.stream_size / sb.channels;
|
||||
vgmstream->num_samples = ps_bytes_to_samples(sb.stream_size, sb.channels) ;
|
||||
break;
|
||||
|
||||
case RAW_XBOX:
|
||||
vgmstream->coding_type = coding_XBOX;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->num_samples = ms_ima_bytes_to_samples(sb.stream_size, 0x24*sb.channels,sb.channels);
|
||||
break;
|
||||
|
||||
case RAW_DSP:
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = sb.stream_size / sb.channels;
|
||||
vgmstream->num_samples = dsp_bytes_to_samples(sb.stream_size, sb.channels);
|
||||
|
||||
{
|
||||
off_t coefs_offset = sb.main_size + sb.section1_size + sb.section2_size + sb.extra_offset;
|
||||
coefs_offset += 0x10; /* entry size is 0x40 (first/last 0x10 = unknown), per channel */
|
||||
|
||||
dsp_read_coefs_be(vgmstream,streamFile,coefs_offset, 0x40);
|
||||
}
|
||||
break;
|
||||
|
||||
case FMT_VAG:
|
||||
/* skip VAG header (some sb4 use VAG and others raw PSX) */
|
||||
if (read_32bitBE(start_offset, streamData) == 0x56414770) { /* "VAGp" */
|
||||
start_offset += 0x30;
|
||||
sb.stream_size -= 0x30;
|
||||
}
|
||||
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = sb.stream_size / sb.channels;
|
||||
vgmstream->num_samples = ps_bytes_to_samples(sb.stream_size, sb.channels) ;
|
||||
if (sb.channels > 1) { VGM_LOG("UBI SB: >1 channel\n"); goto fail; } //todo
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case FMT_AT3: {
|
||||
ffmpeg_codec_data *ffmpeg_data;
|
||||
|
||||
ffmpeg_data = init_ffmpeg_offset(streamData, start_offset, sb.stream_size);
|
||||
if ( !ffmpeg_data ) goto fail;
|
||||
vgmstream->codec_data = ffmpeg_data;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
if (sb.stream_samples == 0) /* sometimes not known */
|
||||
sb.stream_samples = ffmpeg_data->totalSamples;
|
||||
vgmstream->num_samples = sb.stream_samples;
|
||||
if (sb.stream_samples != ffmpeg_data->totalSamples) {
|
||||
VGM_LOG("UBI SB: header samples differ (%i vs %i)\n", sb.stream_samples, (size_t)ffmpeg_data->totalSamples);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* manually read skip_samples if FFmpeg didn't do it */
|
||||
if (ffmpeg_data->skipSamples <= 0) {
|
||||
off_t chunk_offset;
|
||||
size_t chunk_size, fact_skip_samples = 0;
|
||||
if (!find_chunk_le(streamData, 0x66616374,start_offset+0xc,0, &chunk_offset,&chunk_size)) /* find "fact" */
|
||||
goto fail;
|
||||
if (chunk_size == 0x8) {
|
||||
fact_skip_samples = read_32bitLE(chunk_offset+0x4, streamData);
|
||||
} else if (chunk_size == 0xc) {
|
||||
fact_skip_samples = read_32bitLE(chunk_offset+0x8, streamData);
|
||||
}
|
||||
ffmpeg_set_skip_samples(ffmpeg_data, fact_skip_samples);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case FMT_OGG: {
|
||||
ffmpeg_codec_data *ffmpeg_data;
|
||||
|
||||
ffmpeg_data = init_ffmpeg_offset(streamData, start_offset, sb.stream_size);
|
||||
if ( !ffmpeg_data ) goto fail;
|
||||
vgmstream->codec_data = ffmpeg_data;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
vgmstream->num_samples = sb.stream_samples; /* ffmpeg_data->totalSamples */
|
||||
VGM_ASSERT(sb.stream_samples != ffmpeg_data->totalSamples, "UBI SB: header samples differ\n");
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
default:
|
||||
VGM_LOG("UBI SB: unknown codec\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (sb.has_internal_names || sb.is_external) {
|
||||
strcpy(vgmstream->stream_name, sb.stream_name);
|
||||
}
|
||||
|
||||
|
||||
/* open the file for reading (can be an external stream, different from the current .sb0) */
|
||||
if ( !vgmstream_open_stream(vgmstream, streamData, start_offset) )
|
||||
goto fail;
|
||||
|
||||
if (sb.is_external && streamData) close_streamfile(streamData);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
if (sb.is_external && streamData) close_streamfile(streamData);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
|
||||
static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile) {
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
||||
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
|
||||
int i, ok;
|
||||
int target_stream = streamFile->stream_index;
|
||||
|
||||
if (target_stream == 0) target_stream = 1;
|
||||
|
||||
sb->big_endian = check_extensions(streamFile, "sb3,sb6,sb7"); /* GC, PS3, Wii */
|
||||
if (sb->big_endian) {
|
||||
read_32bit = read_32bitBE;
|
||||
read_16bit = read_16bitBE;
|
||||
} else {
|
||||
read_32bit = read_32bitLE;
|
||||
read_16bit = read_16bitLE;
|
||||
}
|
||||
|
||||
/* file layout is: base header, section1, section2, extra section, section3, data (all except base header can be null) */
|
||||
|
||||
sb->version = read_32bit(0x00, streamFile); /* 16b+16b major/minor version */
|
||||
sb->section1_num = read_32bit(0x04, streamFile); /* group headers? */
|
||||
sb->section2_num = read_32bit(0x08, streamFile); /* streams headers (internal or external) */
|
||||
sb->section3_num = read_32bit(0x0c, streamFile); /* internal streams table */
|
||||
sb->extra_size = read_32bit(0x10, streamFile); /* extra table, unknown except with DSP = coefs */
|
||||
sb->flag1 = read_32bit(0x14, streamFile); /* unknown, usually -1 but can be others (0/1/2/etc) */
|
||||
sb->flag2 = read_32bit(0x18, streamFile); /* unknown, usually -1 but can be others */
|
||||
|
||||
ok = config_sb_header_version(sb, streamFile);
|
||||
if (!ok) {
|
||||
VGM_LOG("UBI SB: unknown SB version+platform\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
sb->main_size = 0x1c;
|
||||
sb->section1_size = sb->section1_entry_size * sb->section1_num;
|
||||
sb->section2_size = sb->section2_entry_size * sb->section2_num;
|
||||
sb->section3_size = sb->section3_entry_size * sb->section3_num;
|
||||
|
||||
/* find target stream info in section2 */
|
||||
for (i = 0; i < sb->section2_num; i++) {
|
||||
off_t offset = sb->main_size + sb->section1_size + sb->section2_entry_size*i;
|
||||
|
||||
/* ignore non-audio entry (other types seem to have config data) */
|
||||
if (read_32bit(offset + 0x04, streamFile) != 0x01)
|
||||
continue;
|
||||
|
||||
/* update streams (total_stream also doubles as current) */
|
||||
sb->total_streams++;
|
||||
if (sb->total_streams != target_stream)
|
||||
continue;
|
||||
;VGM_LOG("target at offset=%lx (size=%x)\n", offset, sb->section2_entry_size);
|
||||
|
||||
sb->header_id = read_32bit(offset + 0x00, streamFile); /* 16b+16b group+sound id */
|
||||
sb->header_type = read_32bit(offset + 0x04, streamFile);
|
||||
sb->stream_size = read_32bit(offset + 0x08, streamFile);
|
||||
sb->extra_offset = read_32bit(offset + 0x0c, streamFile); /* within the extra section */
|
||||
sb->stream_offset = read_32bit(offset + 0x10, streamFile); /* within the data section */
|
||||
sb->channels = (sb->has_short_channels) ?
|
||||
(uint16_t)read_16bit(offset + sb->channels_offset, streamFile) :
|
||||
(uint32_t)read_32bit(offset + sb->channels_offset, streamFile);
|
||||
sb->sample_rate = read_32bit(offset + sb->sample_rate_offset, streamFile);
|
||||
sb->stream_type = read_32bit(offset + sb->stream_type_offset, streamFile);
|
||||
|
||||
if (sb->num_samples_offset)
|
||||
sb->stream_samples = read_32bit(offset + sb->num_samples_offset, streamFile);
|
||||
|
||||
if (sb->stream_id_offset)
|
||||
sb->stream_id = read_32bit(offset + sb->stream_id_offset, streamFile);
|
||||
|
||||
/* external stream name can be found in the header (first versions) or the extra table (later versions) */
|
||||
if (sb->stream_name_offset) {
|
||||
read_string(sb->stream_name, sb->stream_name_size, offset + sb->stream_name_offset, streamFile);
|
||||
} else {
|
||||
sb->stream_name_offset = read_32bit(offset + sb->extra_name_offset, streamFile);
|
||||
read_string(sb->stream_name, sb->stream_name_size, sb->main_size + sb->section1_size + sb->section2_size + sb->stream_name_offset, streamFile);
|
||||
}
|
||||
|
||||
/* not always set and must be derived */
|
||||
if (sb->external_flag_offset) {
|
||||
sb->is_external = read_32bit(offset + sb->external_flag_offset, streamFile);
|
||||
} else if (sb->section3_num == 0) {
|
||||
sb->is_external = 1;
|
||||
} else {
|
||||
sb->autodetect_external = 1;
|
||||
|
||||
if (sb->stream_name[0] == '\0')
|
||||
sb->autodetect_external = 0; /* no name */
|
||||
if (sb->extra_size > 0 && sb->stream_name_offset > sb->extra_size)
|
||||
sb->autodetect_external = 0; /* name outside extra table == is internal */
|
||||
}
|
||||
}
|
||||
if (sb->total_streams == 0) {
|
||||
VGM_LOG("UBI SB: no streams\n");
|
||||
goto fail;
|
||||
}
|
||||
if (target_stream < 0 || target_stream > sb->total_streams || sb->total_streams < 1) {
|
||||
VGM_LOG("UBI SB: wrong target stream (target=%i, total=%i)\n", target_stream, sb->total_streams);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!sb->stream_id_offset && sb->section3_num > 1) {
|
||||
VGM_LOG("UBI SB: unexpected number of internal streams %i\n", sb->section3_num);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
/* happens in some versions */
|
||||
if (sb->stream_type > 0xFF) {
|
||||
VGM_LOG("UBI SB: garbage in stream_type\n");
|
||||
sb->stream_type = 0;
|
||||
}
|
||||
|
||||
/* guess codec */
|
||||
switch(sb->stream_type) {
|
||||
case 0x00: /* platform default (rarely external) */
|
||||
if ( check_extensions(streamFile, "sb0")) {
|
||||
sb->codec = RAW_PCM;
|
||||
} else if (check_extensions(streamFile, "sb1,sb5")) {
|
||||
sb->codec = RAW_PSX;
|
||||
} else if (check_extensions(streamFile, "sb2")) {
|
||||
sb->codec = RAW_XBOX;
|
||||
} else if (check_extensions(streamFile, "sb3,sb7")) {
|
||||
sb->codec = RAW_DSP;
|
||||
} else if (check_extensions(streamFile, "sb4")) {
|
||||
sb->codec = FMT_VAG;
|
||||
} else {
|
||||
VGM_LOG("UBI SB: unknown internal format\n");
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x03: /* Ubi ADPCM (main external stream codec, has subtypes) */
|
||||
sb->codec = UBI_ADPCM;
|
||||
break;
|
||||
|
||||
case 0x04: /* Ogg (later PC games) */
|
||||
sb->codec = FMT_OGG;
|
||||
break;
|
||||
|
||||
case 0x05: /* AT3 (PSP, PS3) */
|
||||
sb->codec = FMT_AT3;
|
||||
break;
|
||||
|
||||
case 0x01: /* PCM? (Wii, rarely used) */
|
||||
default:
|
||||
VGM_LOG("UBI SB: unknown stream_type %x\n", sb->stream_type);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* uncommon but possible */
|
||||
//VGM_ASSERT(sb->is_external && sb->section3_num != 0, "UBI SS: mixed external and internal streams\n");
|
||||
|
||||
/* seems can be safely ignored */
|
||||
//VGM_ASSERT(sb->is_external && sb->stream_id_offset && sb->stream_id > 0, "UBI SB: unexpected external stream with stream id\n");
|
||||
|
||||
//todo fix, wrong
|
||||
/* section 3: substreams within the file, adjust stream offset (rarely used but table is always present) */
|
||||
if (!sb->is_external && sb->stream_id_offset && sb->section3_num > 1) {
|
||||
for (i = 0; i < sb->section3_num; i++) {
|
||||
off_t offset = sb->main_size + sb->section1_size + sb->section2_size + sb->extra_size + sb->section3_entry_size * i;
|
||||
|
||||
/* table has unordered ids+size, so if our id doesn't match current data offset must be beyond */
|
||||
if (read_32bit(offset + 0x00, streamFile) == sb->stream_id)
|
||||
break;
|
||||
sb->stream_offset += read_32bit(offset + 0x04, streamFile);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
|
||||
/* meh... */
|
||||
int is_sb0 = check_extensions(streamFile, "sb0"); /* PC */
|
||||
int is_sb1 = check_extensions(streamFile, "sb1"); /* PS2 */
|
||||
int is_sb2 = check_extensions(streamFile, "sb2"); /* Xbox */
|
||||
int is_sb3 = check_extensions(streamFile, "sb3"); /* GC */
|
||||
int is_sb4 = check_extensions(streamFile, "sb4"); /* PSP, X360? */
|
||||
int is_sb5 = check_extensions(streamFile, "sb5"); /* PSP, 3DS? */
|
||||
//int is_sb6 = check_extensions(streamFile, "sb6"); /* PS3? */
|
||||
int is_sb7 = check_extensions(streamFile, "sb7"); /* Wii */
|
||||
|
||||
|
||||
/* The format varies with almost every game + platform (some kind of class serialization?),
|
||||
* support is done case-by-case as offsets move slightly. Basic layout:
|
||||
* - fixed part (0x00..0x14)
|
||||
* - garbage, flags
|
||||
* - external stream number of samples / internal garbage
|
||||
* - external stream size (also in the common part) / internal garbage
|
||||
* - garbage
|
||||
* - external original sample rate / internal garbage
|
||||
* - sample rate, pcm bits?, channels
|
||||
* - stream type and external filename (internal filename too on some platforms)
|
||||
* - end flags/garbage
|
||||
* Garbage looks like uninitialized variables (may be null, contain part of strings, etc).
|
||||
* Later version don't have filename per header but in the extra section.
|
||||
*/
|
||||
|
||||
|
||||
/* common */
|
||||
sb->section3_entry_size = 0x08;
|
||||
sb->stream_name_size = 0x24; /* maybe 0x28 or 0x20 for some but ok enough (null terminated) */
|
||||
|
||||
/* Prince of Persia: Sands of Time (2003)(PC) */
|
||||
if ((sb->version == 0x000A0002 && is_sb0) || /* (not sure if exists, just in case) */
|
||||
(sb->version == 0x000A0004 && is_sb0)) { /* main game */
|
||||
sb->section1_entry_size = 0x64;
|
||||
sb->section2_entry_size = 0x80;
|
||||
|
||||
sb->external_flag_offset = 0x24; /* maybe 0x28 */
|
||||
sb->num_samples_offset = 0x30;
|
||||
sb->sample_rate_offset = 0x44;
|
||||
sb->channels_offset = 0x4a;
|
||||
sb->stream_type_offset = 0x4c;
|
||||
sb->stream_name_offset = 0x50;
|
||||
|
||||
sb->has_short_channels = 1;
|
||||
sb->has_internal_names = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: Sands of Time (2003)(PS2) */
|
||||
if ((sb->version == 0x000A0002 && is_sb1) || /* Prince of Persia 1 port */
|
||||
(sb->version == 0x000A0004 && is_sb1)) { /* main game */
|
||||
sb->section1_entry_size = 0x48;
|
||||
sb->section2_entry_size = 0x6c;
|
||||
|
||||
sb->external_flag_offset = 0; /* no apparent flag */
|
||||
sb->channels_offset = 0x20;
|
||||
sb->sample_rate_offset = 0x24;
|
||||
sb->num_samples_offset = 0x30;
|
||||
sb->stream_name_offset = 0x40;
|
||||
sb->stream_type_offset = 0x68;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: Sands of Time (2003)(Xbox) */
|
||||
if ((sb->version == 0x000A0002 && is_sb2) || /* Prince of Persia 1 port */
|
||||
(sb->version == 0x000A0004 && is_sb2)) { /* main game */
|
||||
sb->section1_entry_size = 0x64;
|
||||
sb->section2_entry_size = 0x78;
|
||||
|
||||
sb->external_flag_offset = 0x24; /* maybe 0x28 */
|
||||
sb->num_samples_offset = 0x30;
|
||||
sb->sample_rate_offset = 0x44;
|
||||
sb->channels_offset = 0x4a;
|
||||
sb->stream_type_offset = 0x4c; /* may contain garbage */
|
||||
sb->stream_name_offset = 0x50;
|
||||
|
||||
sb->has_short_channels = 1;
|
||||
sb->has_internal_names = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: Sands of Time (2003)(GC) */
|
||||
if ((sb->version == 0x000A0002 && is_sb3) || /* Prince of Persia 1 port */
|
||||
(sb->version == 0x000A0004 && is_sb3)) { /* main game */
|
||||
sb->section1_entry_size = 0x64;
|
||||
sb->section2_entry_size = 0x74;
|
||||
|
||||
sb->external_flag_offset = 0x20; /* maybe 0x24 */
|
||||
sb->num_samples_offset = 0x2c;
|
||||
sb->sample_rate_offset = 0x40;
|
||||
sb->channels_offset = 0x46;
|
||||
sb->stream_type_offset = 0x48;
|
||||
sb->stream_name_offset = 0x4c;
|
||||
|
||||
sb->has_short_channels = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: Warrior Within (2004)(PC) */
|
||||
if (sb->version == 0x00120009 && is_sb0) {
|
||||
sb->section1_entry_size = 0x6c;
|
||||
sb->section2_entry_size = 0x84;
|
||||
|
||||
sb->external_flag_offset = 0x24;
|
||||
sb->num_samples_offset = 0x30;
|
||||
sb->sample_rate_offset = 0x44;
|
||||
sb->channels_offset = 0x4c;
|
||||
sb->stream_type_offset = 0x50;
|
||||
sb->stream_name_offset = 0x54;
|
||||
|
||||
sb->has_internal_names = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: Warrior Within (2004)(PS2) */
|
||||
if (sb->version == 0x00120009 && is_sb1) {
|
||||
sb->section1_entry_size = 0x48;
|
||||
sb->section2_entry_size = 0x6c;
|
||||
|
||||
sb->external_flag_offset = 0; /* no apparent flag */
|
||||
sb->channels_offset = 0x20;
|
||||
sb->sample_rate_offset = 0x24;
|
||||
sb->num_samples_offset = 0x30;
|
||||
sb->stream_name_offset = 0x40;
|
||||
sb->stream_type_offset = 0x68;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: Warrior Within (2004)(Xbox) */
|
||||
if (sb->version == 0x00120009 && is_sb2) {
|
||||
sb->section1_entry_size = 0x6c;
|
||||
sb->section2_entry_size = 0x90;
|
||||
|
||||
sb->external_flag_offset = 0x24;
|
||||
sb->num_samples_offset = 0x44;
|
||||
sb->sample_rate_offset = 0x58;
|
||||
sb->channels_offset = 0x60;
|
||||
sb->stream_type_offset = 0x64; /* may contain garbage */
|
||||
sb->stream_name_offset = 0x68;
|
||||
|
||||
sb->has_internal_names = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: Warrior Within (2004)(GC) */
|
||||
if (sb->version == 0x00120009 && is_sb3) {
|
||||
sb->section1_entry_size = 0x6c;
|
||||
sb->section2_entry_size = 0x78;
|
||||
|
||||
sb->external_flag_offset = 0x20;
|
||||
sb->num_samples_offset = 0x2c;
|
||||
sb->sample_rate_offset = 0x40;
|
||||
sb->channels_offset = 0x48;
|
||||
sb->stream_type_offset = 0x4c;
|
||||
sb->stream_name_offset = 0x50;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: Revelations (2005)(PSP) */
|
||||
if (sb->version == 0x0012000C && is_sb4) {
|
||||
sb->section1_entry_size = 0x68;
|
||||
sb->section2_entry_size = 0x84;
|
||||
|
||||
sb->external_flag_offset = 0x24;
|
||||
sb->num_samples_offset = 0x30;
|
||||
sb->sample_rate_offset = 0x44;
|
||||
sb->channels_offset = 0x4c;
|
||||
sb->stream_type_offset = 0x50;
|
||||
sb->stream_name_offset = 0x54;
|
||||
|
||||
sb->has_internal_names = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: The Two Thrones (2005)(PC) */
|
||||
if (sb->version == 0x00150000 && is_sb0) {
|
||||
sb->section1_entry_size = 0x68;
|
||||
sb->section2_entry_size = 0x78;
|
||||
|
||||
sb->external_flag_offset = 0x2c;
|
||||
sb->stream_id_offset = 0x34;//todo test
|
||||
sb->num_samples_offset = 0x40;
|
||||
sb->sample_rate_offset = 0x54;
|
||||
sb->channels_offset = 0x5c;
|
||||
sb->stream_type_offset = 0x60;
|
||||
sb->extra_name_offset = 0x64;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: The Two Thrones (2005)(PS2) */
|
||||
if (sb->version == 0x00150000 && is_sb1) {
|
||||
sb->section1_entry_size = 0x48;
|
||||
sb->section2_entry_size = 0x5c;
|
||||
|
||||
sb->external_flag_offset = 0; /* no apparent flag */
|
||||
sb->channels_offset = 0x2c;
|
||||
sb->sample_rate_offset = 0x30;
|
||||
sb->num_samples_offset = 0x3c;
|
||||
sb->extra_name_offset = 0x4c;
|
||||
sb->stream_type_offset = 0x50;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: The Two Thrones (2005)(Xbox) */
|
||||
if (sb->version == 0x00150000 && is_sb2) {
|
||||
sb->section1_entry_size = 0x48;
|
||||
sb->section2_entry_size = 0x58;
|
||||
|
||||
sb->external_flag_offset = 0; /* no apparent flag */
|
||||
sb->num_samples_offset = 0x28;
|
||||
sb->stream_id_offset = 0x34;//todo test
|
||||
sb->sample_rate_offset = 0x3c;
|
||||
sb->channels_offset = 0x44;
|
||||
sb->stream_type_offset = 0x48;
|
||||
sb->extra_name_offset = 0x4c;
|
||||
|
||||
sb->has_internal_names = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: The Two Thrones (2005)(GC) */
|
||||
if (sb->version == 0x00150000 && is_sb3) {
|
||||
sb->section1_entry_size = 0x68;
|
||||
sb->section2_entry_size = 0x6c;
|
||||
|
||||
sb->external_flag_offset = 0x28; /* maybe 0x2c */
|
||||
sb->num_samples_offset = 0x3c;
|
||||
sb->sample_rate_offset = 0x50;
|
||||
sb->channels_offset = 0x58;
|
||||
sb->stream_type_offset = 0x5c;
|
||||
sb->extra_name_offset = 0x60;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: Rival Swords (2007)(PSP) */
|
||||
if (sb->version == 0x00180005 && is_sb5) {
|
||||
sb->section1_entry_size = 0x48;
|
||||
sb->section2_entry_size = 0x54;
|
||||
|
||||
sb->external_flag_offset = 0; /* no apparent flag */
|
||||
sb->channels_offset = 0x28;
|
||||
sb->sample_rate_offset = 0x2c;
|
||||
//sb->num_samples_offset = 0x34 or 0x3c /* varies */
|
||||
sb->extra_name_offset = 0x44;
|
||||
sb->stream_type_offset = 0x48;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: Rival Swords (2007)(Wii) */
|
||||
if (sb->version == 0x00190003 && is_sb7) {
|
||||
sb->section1_entry_size = 0x68;
|
||||
sb->section2_entry_size = 0x70;
|
||||
|
||||
sb->external_flag_offset = 0x28; /* maybe 0x2c */
|
||||
sb->channels_offset = 0x3c;
|
||||
sb->sample_rate_offset = 0x44;
|
||||
sb->num_samples_offset = 0x48;
|
||||
sb->extra_name_offset = 0x58;
|
||||
sb->stream_type_offset = 0x5c;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -392,6 +392,28 @@ STREAMFILE * open_stream_ext(STREAMFILE *streamFile, const char * ext) {
|
||||
return streamFile->open(streamFile,filename_ext,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/* Opens an stream in the same folder */
|
||||
STREAMFILE * open_stream_name(STREAMFILE *streamFile, const char * name) {
|
||||
char foldername[PATH_LIMIT];
|
||||
char filename[PATH_LIMIT];
|
||||
const char *path;
|
||||
|
||||
streamFile->get_name(streamFile,foldername,sizeof(foldername));
|
||||
|
||||
path = strrchr(foldername,DIR_SEPARATOR);
|
||||
if (path!=NULL) path = path+1;
|
||||
|
||||
if (path) {
|
||||
strcpy(filename, foldername);
|
||||
filename[path-foldername] = '\0';
|
||||
strcat(filename, name);
|
||||
} else {
|
||||
strcpy(filename, name);
|
||||
}
|
||||
|
||||
return streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* open file containing decryption keys and copy to buffer
|
||||
* tries combinations of keynames based on the original filename
|
||||
|
@ -159,6 +159,7 @@ static inline int8_t read_8bit(off_t offset, STREAMFILE * streamfile) {
|
||||
size_t get_streamfile_dos_line(int dst_length, char * dst, off_t offset, STREAMFILE * infile, int *line_done_ptr);
|
||||
|
||||
STREAMFILE * open_stream_ext(STREAMFILE *streamFile, const char * ext);
|
||||
STREAMFILE * open_stream_name(STREAMFILE *streamFile, const char * ext);
|
||||
|
||||
int read_string(char * buf, size_t bufsize, off_t offset, STREAMFILE *streamFile);
|
||||
|
||||
|
@ -370,6 +370,7 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = {
|
||||
init_vgmstream_nsw_opus,
|
||||
init_vgmstream_pc_al2,
|
||||
init_vgmstream_pc_ast,
|
||||
init_vgmstream_ubi_sb,
|
||||
|
||||
init_vgmstream_txth, /* should go at the end (lower priority) */
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
@ -1024,6 +1025,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
|
||||
case coding_SNDS_IMA:
|
||||
case coding_IMA:
|
||||
case coding_OTNS_IMA:
|
||||
case coding_UBI_IMA:
|
||||
return 1;
|
||||
case coding_IMA_int:
|
||||
case coding_DVI_IMA_int:
|
||||
@ -1182,6 +1184,7 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
|
||||
case coding_G721:
|
||||
case coding_SNDS_IMA:
|
||||
case coding_OTNS_IMA:
|
||||
case coding_UBI_IMA: /* variable (PCM then IMA) */
|
||||
return 0;
|
||||
case coding_NGC_AFC:
|
||||
return 9;
|
||||
@ -1682,6 +1685,13 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
|
||||
samples_to_do);
|
||||
}
|
||||
break;
|
||||
case coding_UBI_IMA:
|
||||
for (chan=0;chan<vgmstream->channels;chan++) {
|
||||
decode_ubi_ima(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,
|
||||
vgmstream->channels,vgmstream->samples_into_block,
|
||||
samples_to_do,chan);
|
||||
}
|
||||
break;
|
||||
|
||||
case coding_WS:
|
||||
for (chan=0;chan<vgmstream->channels;chan++) {
|
||||
|
@ -135,6 +135,7 @@ typedef enum {
|
||||
coding_WWISE_IMA, /* Audiokinetic Wwise IMA ADPCM */
|
||||
coding_REF_IMA, /* Reflections IMA ADPCM */
|
||||
coding_AWC_IMA, /* Rockstar AWC IMA ADPCM */
|
||||
coding_UBI_IMA, /* Ubisoft IMA ADPCM */
|
||||
|
||||
coding_MSADPCM, /* Microsoft ADPCM */
|
||||
coding_WS, /* Westwood Studios VBR ADPCM */
|
||||
@ -630,6 +631,7 @@ typedef enum {
|
||||
meta_NSW_OPUS, /* Lego City Undercover (Switch) */
|
||||
meta_PC_AL2, /* Conquest of Elysium 3 (PC) */
|
||||
meta_PC_AST, /* Dead Rising (PC) */
|
||||
meta_UBI_SB, /* Ubisoft banks */
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
meta_OGG_VORBIS, /* Ogg Vorbis */
|
||||
|
Loading…
Reference in New Issue
Block a user