Merge pull request #355 from bnnm/ubi-mic-ue4msadpcm

ubi mic ue4msadpcm
This commit is contained in:
Christopher Snowhill 2019-01-20 18:10:46 -08:00 committed by GitHub
commit 199e786f94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1771 additions and 1620 deletions

View File

@ -132,7 +132,7 @@ them playable through vgmstream.
- .aac to .laac (tri-Ace games)
- .ac3 to .lac3 (standard AC3)
- .aif to .aiffl or .aifcl (standard Mac AIF)
- .asf to .sng (EA games)
- .asf to .lasf (EA games, Argonaut ASF)
- .flac to .lflac (standard FLAC)
- .mp2 to .lmp2 (standard MP2)
- .mp3 to .lmp3 (standard MP3)

View File

@ -10,7 +10,7 @@ When an unsupported file is loaded (for instance "bgm01.snd"), vgmstream tries t
If found and parsed correctly (the TXTH may be rejected if incorrect commands are found) vgmstream will try to play the file as described. Extension must be accepted/added to vgmstream (plugins like foobar2000 only load extensions from a whitelist in formats.c), or one could rename to any supported extension (like .vgmstream), or leave the file extensionless.
You can also use ".(sub).(ext).txth" (if the file is "filename.sub.ext"), to allow mixing slightly different files in the same folder. The "sub" part doesn't need to be an extension, for example:
- 001.1ch.str, 001.1ch.str may use .1ch.txth
- 001.1ch.str, 001.1ch.str may use .1ch.txth
- 003.2ch.str, 003.2ch.str may use .2ch.txth
- etc
@ -51,7 +51,7 @@ A text file with the above commands must be saved as ".vag.txth" or ".txth", not
# * $1|2|3|4: value has size of 8/16/24/32 bit (optional, defaults to 4)
# Examples: @0x10:BE$2 (get big endian 16b value at 0x10)
# - (field): uses current value of a field. Accepted strings:
# - interleave, channels, sample_rate
# - interleave, interleave_last, channels, sample_rate
# - start_offset, data_size
# - num_samples, loop_start_sample, loop_end_sample
# - subsong_count, subsong_offset
@ -116,6 +116,14 @@ value_sub|value_- = (number)|(offset)|(field)
# Interleave 0 means "stereo mode" for some codecs (IMA, AICA, etc).
interleave = (number)|(offset)|(field)|half_size
# Interleave in the last block [OPTIONAL]
# - auto: calculate based on channels, interleave and data_size/start_offset
# In some files with interleaved data the last block is smaller than interleave,
# so interleave must be smaller in the last block. This fixes decoding glitches
# for those files. Note that this doesn't affect files with padding data in the
# last block (as the interleave itself is constant).
interleave_last = (number)|(auto)
# Validate that id_value matches value at id_offset [OPTIONAL]
# Can be redefined several times, it's checked whenever a new id_offset is found.
id_value = (number)|(offset)|(field)

View File

@ -206,6 +206,7 @@ static const char* extension_list[] = {
"l",
"laac", //fake extension for .aac (tri-Ace)
"lac3", //fake extension for .ac3, FFmpeg/not parsed
"lasf", //fake extension for .asf (various)
"leg",
"lflac", //fake extension for .flac, FFmpeg/not parsed
"lin",
@ -620,6 +621,7 @@ static const coding_info coding_info_list[] = {
{coding_H4M_IMA, "Hudson HVQM4 4-bit IMA ADPCM"},
{coding_MSADPCM, "Microsoft 4-bit ADPCM"},
{coding_MSADPCM_int, "Microsoft 4-bit ADPCM (mono/interleave)"},
{coding_MSADPCM_ck, "Microsoft 4-bit ADPCM (Cricket Audio)"},
{coding_WS, "Westwood Studios VBR ADPCM"},
{coding_AICA, "Yamaha AICA 4-bit ADPCM"},
@ -769,7 +771,7 @@ static const meta_info meta_info_list[] = {
{meta_PS_HEADERLESS, "Headerless PS-ADPCM raw header"},
{meta_PS2_MIB_MIH, "Sony MultiStream MIH+MIB header"},
{meta_DSP_MPDSP, "Single DSP header stereo by .mpdsp extension"},
{meta_PS2_MIC, "assume KOEI MIC file by .mic extension"},
{meta_PS2_MIC, "KOEI .MIC header"},
{meta_DSP_JETTERS, "Double DSP header stereo by _lr.dsp extension"},
{meta_DSP_MSS, "Double DSP header stereo by .mss extension"},
{meta_DSP_GCM, "Double DSP header stereo by .gcm extension"},

View File

@ -272,6 +272,10 @@
RelativePath=".\meta\sqex_scd_streamfile.h"
>
</File>
<File
RelativePath=".\meta\ubi_sb_streamfile.h"
>
</File>
<File
RelativePath=".\meta\ubi_lyn_ogg_streamfile.h"
>

View File

@ -110,6 +110,7 @@
<ClInclude Include="meta\vsv_streamfile.h" />
<ClInclude Include="meta\opus_interleave_streamfile.h" />
<ClInclude Include="meta\sqex_scd_streamfile.h" />
<ClInclude Include="meta\ubi_sb_streamfile.h" />
<ClInclude Include="meta\ubi_lyn_ogg_streamfile.h" />
<ClInclude Include="meta\meta.h" />
<ClInclude Include="meta\hca_keys.h" />

View File

@ -104,6 +104,9 @@
<ClInclude Include="meta\sqex_scd_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\ubi_sb_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\ubi_lyn_ogg_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>

View File

@ -9,7 +9,9 @@ VGMSTREAM * init_vgmstream_asf(STREAMFILE *streamFile) {
/* checks */
if (!check_extensions(streamFile, "asf"))
/* .asf: original
* .lasf: fake for plugins */
if (!check_extensions(streamFile, "asf,lasf"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x41534600) /* "ASF\0" */

View File

@ -40,11 +40,12 @@ VGMSTREAM * init_vgmstream_ea_1snh(STREAMFILE *streamFile) {
/* checks */
/* .asf/as4: common
/* .asf/as4: common,
* .lasf: fake for plugins
* .cnk: some PS games
* .sng: fake for plugins (to mimic EA SCHl's common extension)
* .uv/tgq: some SAT games (video only?) */
if (!check_extensions(streamFile,"asf,as4,cnk,sng,uv,tgq"))
if (!check_extensions(streamFile,"asf,lasf,as4,cnk,sng,uv,tgq"))
goto fail;
if (read_32bitBE(0x00,streamFile) != 0x31534E68 && /* "1SNh" */

View File

@ -577,7 +577,7 @@ VGMSTREAM * init_vgmstream_ea_sbr_harmony(STREAMFILE *streamFile) {
off_t data_offset, table_offset, dset_offset, base_offset, sound_table_offset, sound_offset, header_offset, start_offset;
STREAMFILE *sbsFile = NULL, *streamData = NULL;
VGMSTREAM *vgmstream = NULL;
int target_stream = streamFile->stream_index, total_sounds, local_target, is_streamed;
int target_stream = streamFile->stream_index, total_sounds, local_target, is_streamed = 0;
int32_t(*read_32bit)(off_t, STREAMFILE*);
int16_t(*read_16bit)(off_t, STREAMFILE*);

View File

@ -116,6 +116,7 @@ VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) {
/* check extension */
/* they don't seem enforced by EA's tools but usually:
* .asf: ~early (audio stream file?) [ex. Need for Speed (PC)]
* .lasf: fake for plugins
* .str: ~early [ex. FIFA 2002 (PS1)]
* .eam: ~mid (fake?)
* .exa: ~mid [ex. 007 - From Russia with Love]
@ -129,7 +130,7 @@ VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) {
* .gsf: 007 - Everything or Nothing (GC)
* .mus: map/mpf+mus only?
* (extensionless): SSX (PS2) (inside .big) */
if (!check_extensions(streamFile,"asf,str,eam,exa,sng,aud,sx,xa,strm,stm,hab,xsf,gsf,mus,"))
if (!check_extensions(streamFile,"asf,lasf,str,eam,exa,sng,aud,sx,xa,strm,stm,hab,xsf,gsf,mus,"))
goto fail;
/* check header */

View File

@ -29,8 +29,10 @@ VGMSTREAM * init_vgmstream_ea_schl_fixed(STREAMFILE *streamFile) {
ea_header ea = {0};
/* check extension */
if (!check_extensions(streamFile,"asf"))
/* checks */
/* .asf: original
* .lasf: fake for plugins */
if (!check_extensions(streamFile,"asf,lasf"))
goto fail;
/* check header (see ea_schl.c for more info about blocks) */

View File

@ -39,8 +39,9 @@ typedef enum {
typedef struct {
genh_type codec;
int codec_mode;
size_t interleave;
size_t interleave;
size_t interleave_last;
int channels;
int32_t sample_rate;
@ -153,6 +154,7 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) {
case coding_AICA:
case coding_APPLE_IMA4:
vgmstream->interleave_block_size = genh.interleave;
vgmstream->interleave_last_block_size = genh.interleave_last;
if (vgmstream->channels > 1)
{
if (coding == coding_SDX2) {
@ -202,6 +204,7 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) {
case coding_PCFX:
vgmstream->interleave_block_size = genh.interleave;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_last_block_size = genh.interleave_last;
if (genh.codec_mode >= 0 && genh.codec_mode <= 3)
vgmstream->codec_config = genh.codec_mode;
break;
@ -227,11 +230,13 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) {
if (genh.codec_mode == 1) { /* mono interleave */
coding = coding_XBOX_IMA_int;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_last_block_size = genh.interleave_last;
vgmstream->interleave_block_size = genh.interleave;
}
else { /* 1ch mono, or stereo interleave */
vgmstream->layout_type = genh.interleave ? layout_interleave : layout_none;
vgmstream->interleave_block_size = genh.interleave;
vgmstream->interleave_last_block_size = genh.interleave_last;
if (vgmstream->channels > 2 && vgmstream->channels % 2 != 0)
goto fail; /* only 2ch+..+2ch layout is known */
}
@ -245,6 +250,7 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) {
if (!genh.interleave) goto fail;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = genh.interleave;
vgmstream->interleave_last_block_size = genh.interleave_last;
} else if (genh.coef_interleave_type == 1) {
if (!genh.interleave) goto fail;
coding = coding_NGC_DSP_subint;
@ -419,8 +425,8 @@ static int parse_genh(STREAMFILE * streamFile, genh_header * genh) {
genh->coef_split_spacing = read_32bitLE(0x38,streamFile);
}
/* extended fields */
if (header_size >= 0x54) {
/* extended + reserved fields */
if (header_size >= 0x100) {
genh->num_samples = read_32bitLE(0x40,streamFile);
genh->skip_samples = read_32bitLE(0x44,streamFile); /* for FFmpeg based codecs */
genh->skip_samples_mode = read_8bit(0x48,streamFile); /* 0=autodetect, 1=force manual value @ 0x44 */
@ -430,6 +436,7 @@ static int parse_genh(STREAMFILE * streamFile, genh_header * genh) {
if ((genh->codec == XMA1 || genh->codec == XMA2) && genh->codec_mode==0)
genh->codec_mode = read_8bit(0x4a,streamFile);
genh->data_size = read_32bitLE(0x50,streamFile);
genh->interleave_last = read_32bitLE(0x54,streamFile);
}
if (genh->data_size == 0)

View File

@ -1,75 +1,48 @@
#include "meta.h"
#include "../util.h"
/* MIC
PS2 MIC format is an interleaved format found in most of KOEI Games
The header always start the long value 0x800 which is the start
of the BGM datas.
2008-05-15 - Fastelbja : First version ...
*/
#include "../coding/coding.h"
/* .MIC - from KOEI games [Crimson Sea 2 (PS2), Dynasty Tactics 2 (PS2)] */
VGMSTREAM * init_vgmstream_ps2_mic(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
off_t start_offset;
int loop_flag, channel_count, loop_start, loop_end, sample_rate;
size_t interleave, block_size;
int loop_flag=0;
int channel_count;
int i;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("mic",filename_extension(filename))) goto fail;
/* check Header */
if (read_32bitLE(0x00,streamFile) != 0x800)
/* checks */
if (!check_extensions(streamFile, "mic"))
goto fail;
/* check loop */
loop_flag = (read_32bitLE(0x14,streamFile)!=1);
channel_count=read_32bitLE(0x08,streamFile);
start_offset = read_32bitLE(0x00,streamFile);
if (start_offset != 0x800) goto fail;
sample_rate = read_32bitLE(0x04,streamFile);
channel_count = read_32bitLE(0x08,streamFile);
interleave = read_32bitLE(0x0c,streamFile);
loop_end = read_32bitLE(0x10,streamFile);
loop_start = read_32bitLE(0x14,streamFile);
loop_flag = (loop_start != 1);
block_size = interleave * channel_count;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
vgmstream = allocate_vgmstream(channel_count, loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
vgmstream->channels = channel_count;
vgmstream->sample_rate = read_32bitLE(0x04,streamFile);
/* Compression Scheme */
vgmstream->coding_type = coding_PSX;
vgmstream->num_samples = read_32bitLE(0x10,streamFile)*14*channel_count;
/* Get loop point values */
if(vgmstream->loop_flag) {
vgmstream->loop_start_sample = read_32bitLE(0x14,streamFile)*14*channel_count;
vgmstream->loop_end_sample = read_32bitLE(0x10,streamFile)*14*channel_count;
}
vgmstream->interleave_block_size = read_32bitLE(0x0C,streamFile);
vgmstream->layout_type = layout_interleave;
vgmstream->meta_type = meta_PS2_MIC;
vgmstream->sample_rate = sample_rate;
/* open the file for reading by each channel */
{
for (i=0;i<channel_count;i++) {
vgmstream->ch[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
vgmstream->coding_type = coding_PSX;
vgmstream->interleave_block_size = interleave;
vgmstream->layout_type = layout_interleave;
if (!vgmstream->ch[i].streamfile) goto fail;
vgmstream->ch[i].channel_start_offset=
vgmstream->ch[i].offset=
(off_t)(0x800+vgmstream->interleave_block_size*i);
}
}
vgmstream->num_samples = ps_bytes_to_samples(loop_end * block_size, channel_count);
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start * block_size, channel_count);
vgmstream->loop_end_sample = vgmstream->num_samples;
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail;
return vgmstream;
/* clean up anything we may have opened */
fail:
if (vgmstream) close_vgmstream(vgmstream);
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -10,6 +10,7 @@
static VGMSTREAM *parse_riff_ogg(STREAMFILE * streamFile, off_t start_offset, size_t data_size);
#endif
/* return milliseconds */
static long parse_adtl_marker(unsigned char * marker) {
long hh,mm,ss,ms;
@ -236,6 +237,9 @@ fail:
return -1;
}
static int is_ue4_msadpcm(VGMSTREAM* vgmstream, STREAMFILE* streamFile, riff_fmt_chunk* fmt, int fact_sample_count, off_t start_offset);
VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
riff_fmt_chunk fmt = {0};
@ -682,17 +686,74 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
vgmstream->meta_type = meta_RIFF_WAVE_MWV;
}
if ( !vgmstream_open_stream(vgmstream,streamFile,start_offset) )
goto fail;
return vgmstream;
/* UE4 uses half-interleave mono MSADPCM, try to autodetect without breaking normal MSADPCM */
if (fmt.coding_type == coding_MSADPCM && is_ue4_msadpcm(vgmstream, streamFile, &fmt, fact_sample_count, start_offset)) {
int ch;
size_t half_interleave = data_size / vgmstream->channels;
vgmstream->coding_type = coding_MSADPCM_int;
/* only works with half-interleave as frame_size and interleave are merged ATM*/
for (ch = 0; ch < vgmstream->channels; ch++) {
vgmstream->ch[ch].channel_start_offset =
vgmstream->ch[ch].offset = start_offset + half_interleave*ch;
}
}
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}
/* UE4 MSADPCM is quite normal but has a few minor quirks we can use to detect it */
static int is_ue4_msadpcm(VGMSTREAM* vgmstream, STREAMFILE* streamFile, riff_fmt_chunk* fmt, int fact_sample_count, off_t start_offset) {
/* stereo only */
if (fmt->channel_count != 2)
goto fail;
/* UE4 class is "ADPCM", assume it's the extension too */
if (!check_extensions(streamFile, "adpcm"))
goto fail;
/* UE4 encoder doesn't add "fact" */
if (fact_sample_count != 0)
goto fail;
/* fixed block size */
if (fmt->block_size != 0x200)
goto fail;
/* later UE4 versions use 0x36 (at 0x32 may be fact_samples?) */
if (fmt->size != 0x32 && fmt->size != 0x36)
goto fail;
/* size 0x32 in older UE4 matches standard MSADPCM, so add extra detection */
if (fmt->size == 0x32) {
off_t offset = start_offset;
off_t max_offset = 5 * fmt->block_size; /* try N blocks */
if (max_offset > get_streamfile_size(streamFile))
max_offset = get_streamfile_size(streamFile);
/* their encoder doesn't calculate optimal coefs and uses fixed values every frame
* (could do it for fmt size 0x36 too but maybe they'll fix it in the future) */
while (offset <= max_offset) {
if (read_8bit(offset+0x00, streamFile) != 0 || read_16bitLE(offset+0x01, streamFile) != 0x00E6)
goto fail;
offset += fmt->block_size;
}
}
return 1;
fail:
return 0;
}
VGMSTREAM * init_vgmstream_rifx(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
riff_fmt_chunk fmt = {0};

View File

@ -49,6 +49,7 @@ typedef struct {
uint32_t id_offset;
uint32_t interleave;
uint32_t interleave_last;
uint32_t channels;
uint32_t sample_rate;
@ -228,6 +229,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
case coding_AICA:
case coding_APPLE_IMA4:
vgmstream->interleave_block_size = txth.interleave;
vgmstream->interleave_last_block_size = txth.interleave_last;
if (vgmstream->channels > 1)
{
if (coding == coding_SDX2) {
@ -277,6 +279,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
case coding_PCFX:
vgmstream->interleave_block_size = txth.interleave;
vgmstream->interleave_last_block_size = txth.interleave_last;
vgmstream->layout_type = layout_interleave;
if (txth.codec_mode >= 0 && txth.codec_mode <= 3)
vgmstream->codec_config = txth.codec_mode;
@ -304,10 +307,12 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
coding = coding_XBOX_IMA_int;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = txth.interleave;
vgmstream->interleave_last_block_size = txth.interleave_last;
}
else { /* 1ch mono, or stereo interleave */
vgmstream->layout_type = txth.interleave ? layout_interleave : layout_none;
vgmstream->interleave_block_size = txth.interleave;
vgmstream->interleave_last_block_size = txth.interleave_last;
if (vgmstream->channels > 2 && vgmstream->channels % 2 != 0)
goto fail; /* only 2ch+..+2ch layout is known */
}
@ -320,6 +325,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
if (txth.channels > 1 && txth.codec_mode == 0) {
if (!txth.interleave) goto fail;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_last_block_size = txth.interleave_last;
vgmstream->interleave_block_size = txth.interleave;
} else if (txth.channels > 1 && txth.codec_mode == 1) {
if (!txth.interleave) goto fail;
@ -655,6 +661,15 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char
if (!parse_num(txth->streamHead,txth,val, &txth->interleave)) goto fail;
}
}
else if (0==strcmp(key,"interleave_last")) {
if (0==strcmp(val,"auto")) {
if (txth->channels > 0 && txth->interleave > 0)
txth->interleave_last = (txth->data_size % (txth->interleave * txth->channels)) / txth->channels;
}
else {
if (!parse_num(txth->streamHead,txth,val, &txth->interleave_last)) goto fail;
}
}
else if (0==strcmp(key,"channels")) {
if (!parse_num(txth->streamHead,txth,val, &txth->channels)) goto fail;
}
@ -901,6 +916,7 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v
}
else { /* known field */
if (0==strcmp(val,"interleave")) *out_value = txth->interleave;
if (0==strcmp(val,"interleave_last")) *out_value = txth->interleave_last;
else if (0==strcmp(val,"channels")) *out_value = txth->channels;
else if (0==strcmp(val,"sample_rate")) *out_value = txth->sample_rate;
else if (0==strcmp(val,"start_offset")) *out_value = txth->start_offset;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,363 @@
#ifndef _UBI_SB_STREAMFILE_H_
#define _UBI_SB_STREAMFILE_H_
#include "../streamfile.h"
typedef struct {
/* config */
off_t stream_offset;
off_t stream_size;
int layer_number;
int layer_count;
int layer_max;
int big_endian;
/* internal config */
off_t header_next_start; /* offset to header field */
off_t header_sizes_start; /* offset to header table */
off_t header_data_start; /* offset to header data */
off_t block_next_start; /* offset to block field */
off_t block_sizes_start; /* offset to block table */
off_t block_data_start; /* offset to block data */
size_t header_size; /* derived */
/* state */
off_t logical_offset; /* fake offset */
off_t physical_offset; /* actual offset */
size_t block_size; /* current size */
size_t next_block_size; /* next size */
size_t skip_size; /* size from block start to reach data */
size_t data_size; /* usable size in a block */
size_t logical_size;
} ubi_sb_io_data;
static size_t ubi_sb_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, ubi_sb_io_data* data) {
int32_t(*read_32bit)(off_t, STREAMFILE*) = data->big_endian ? read_32bitBE : read_32bitLE;
size_t total_read = 0;
int i;
/* re-start when previous offset (can't map logical<>physical offsets) */
if (data->logical_offset < 0 || offset < data->logical_offset) {
data->physical_offset = data->stream_offset;
data->logical_offset = 0x00;
data->data_size = 0;
/* process header block (slightly different and data size may be 0) */
{
data->block_size = data->header_size;
data->next_block_size = read_32bit(data->physical_offset + data->header_next_start, streamfile);
if (data->header_sizes_start) {
data->skip_size = data->header_data_start;
for (i = 0; i < data->layer_number; i++) {
data->skip_size += read_32bit(data->physical_offset + data->header_sizes_start + i*0x04, streamfile);
}
data->data_size = read_32bit(data->physical_offset + data->header_sizes_start + data->layer_number*0x04, streamfile);
}
if (data->data_size == 0) {
data->physical_offset += data->block_size;
}
}
}
/* read blocks */
while (length > 0) {
/* ignore EOF */
if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) {
break;
}
/* process new block */
if (data->data_size == 0) {
data->block_size = data->next_block_size;
if (data->block_next_start) /* not set when fixed block size */
data->next_block_size = read_32bit(data->physical_offset + data->block_next_start, streamfile);
data->skip_size = data->block_data_start;
for (i = 0; i < data->layer_number; i++) {
data->skip_size += read_32bit(data->physical_offset + data->block_sizes_start + i*0x04, streamfile);
}
data->data_size = read_32bit(data->physical_offset + data->block_sizes_start + data->layer_number*0x04, streamfile);
}
/* move to next block */
if (offset >= data->logical_offset + data->data_size) {
if (data->block_size == 0 || data->block_size == 0xFFFFFFFF)
break;
data->physical_offset += data->block_size;
data->logical_offset += data->data_size;
data->data_size = 0;
continue;
}
/* read data */
{
size_t bytes_consumed, bytes_done, to_read;
bytes_consumed = offset - data->logical_offset;
to_read = data->data_size - bytes_consumed;
if (to_read > length)
to_read = length;
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
total_read += bytes_done;
dest += bytes_done;
offset += bytes_done;
length -= bytes_done;
if (bytes_done != to_read || bytes_done == 0) {
break; /* error/EOF */
}
}
}
return total_read;
}
static size_t ubi_sb_io_size(STREAMFILE *streamfile, ubi_sb_io_data* data) {
uint8_t buf[1];
if (data->logical_size)
return data->logical_size;
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
ubi_sb_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
data->logical_size = data->logical_offset;
return data->logical_size;
}
static int ubi_sb_io_init(STREAMFILE *streamfile, ubi_sb_io_data* data) {
int32_t (*read_32bit)(off_t,STREAMFILE*) = data->big_endian ? read_32bitBE : read_32bitLE;
off_t offset = data->stream_offset;
uint32_t version;
int i;
if (data->stream_offset + data->stream_size > get_streamfile_size(streamfile)) {
VGM_LOG("UBI SB: bad size\n");
goto fail;
}
/* Layers have a main header, then headered blocks with data.
* We configure stuff to unify parsing of all variations. */
version = (uint32_t)read_32bit(offset+0x00, streamfile);
switch(version) {
case 0x00000002: /* Splinter Cell */
/* - layer header
* 0x04: layer count
* 0x08: stream size
* 0x0c: block header size
* 0x10: block size (fixed)
* 0x14: min layer size?
* - block header
* 0x00: block number
* 0x04: block offset
* 0x08+(04*N): layer size per layer
* 0xNN: layer data per layer */
data->layer_max = read_32bit(offset+0x04, streamfile);
data->header_next_start = 0x10;
data->header_sizes_start = 0;
data->header_data_start = 0x18;
data->block_next_start = 0;
data->block_sizes_start = 0x08;
data->block_data_start = 0x08 + data->layer_max*0x04;
break;
case 0x00000004: /* Prince of Persia: Sands of Time, Batman: Rise of Sin Tzu */
/* - layer header
* 0x04: layer count
* 0x08: stream size
* 0x0c: block count
* 0x10: block header size
* 0x14: block size (fixed)
* 0x18: min layer data?
* 0x1c: size of header sizes
* 0x20+(04*N): header size per layer
* - block header
* 0x00: block number
* 0x04: block offset
* 0x08: always 0x03
* 0x0c+(04*N): layer size per layer
* 0xNN: layer data per layer */
data->layer_max = read_32bit(offset+0x04, streamfile);
data->header_next_start = 0x14;
data->header_sizes_start = 0x20;
data->header_data_start = 0x20 + data->layer_max*0x04;
data->block_next_start = 0;
data->block_sizes_start = 0x0c;
data->block_data_start = 0x0c + data->layer_max*0x04;
break;
case 0x00000007: /* Splinter Cell: Essentials, Splinter Cell 3D */
/* - layer header
* 0x04: config?
* 0x08: layer count
* 0x0c: stream size
* 0x10: block count
* 0x14: block header size
* 0x18: block size (fixed)
* 0x1c+(04*8): min layer data? for 8 layers (-1 after layer count)
* 0x3c: size of header sizes
* 0x40+(04*N): header size per layer
* 0xNN: header data per layer
* - block header
* 0x00: block number
* 0x04: block offset
* 0x08: always 0x03
* 0x0c+(04*N): layer size per layer
* 0xNN: layer data per layer */
data->layer_max = read_32bit(offset+0x08, streamfile);
data->header_next_start = 0x18;
data->header_sizes_start = 0x40;
data->header_data_start = 0x40 + data->layer_max*0x04;
data->block_next_start = 0;
data->block_sizes_start = 0x0c;
data->block_data_start = 0x0c + data->layer_max*0x04;
break;
case 0x00040008: /* Assassin's Creed */
case 0x000B0008: /* Open Season, Surf's Up, TMNT, Splinter Cell HD */
case 0x000C0008: /* Splinter Cell: Double Agent */
case 0x00100008: /* Rainbow Six 2 */
/* - layer header
* 0x04: config?
* 0x08: layer count
* 0x0c: blocks count
* 0x10: block header size
* 0x14: size of header sizes/data
* 0x18: next block size
* 0x1c+(04*N): layer header size
* 0xNN: header data per layer
* - block header:
* 0x00: always 0x03
* 0x04: next block size
* 0x08+(04*N): layer size per layer
* 0xNN: layer data per layer */
data->layer_max = read_32bit(offset+0x08, streamfile);
data->header_next_start = 0x18;
data->header_sizes_start = 0x1c;
data->header_data_start = 0x1c + data->layer_max*0x04;
data->block_next_start = 0x04;
data->block_sizes_start = 0x08;
data->block_data_start = 0x08 + data->layer_max*0x04;
break;
case 0x00100009: /* Splinter Cell: Pandora Tomorrow HD, Prince of Persia 2008, Scott Pilgrim */
/* - layer header
* 0x04: config?
* 0x08: layer count
* 0x0c: blocks count
* 0x10: block header size
* 0x14: size of header sizes/data
* 0x18: next block size
* 0x1c+(04*10): usable size per layer
* 0x5c+(04*N): layer header size
* 0xNN: header data per layer
* - block header:
* 0x00: always 0x03
* 0x04: next block size
* 0x08+(04*N): layer size per layer
* 0xNN: layer data per layer */
data->layer_max = read_32bit(offset+0x08, streamfile);
data->header_next_start = 0x18;
data->header_sizes_start = 0x5c;
data->header_data_start = 0x5c + data->layer_max*0x04;
data->block_next_start = 0x04;
data->block_sizes_start = 0x08;
data->block_data_start = 0x08 + data->layer_max*0x04;
break;
default:
VGM_LOG("UBI SB: unknown layer header %08x\n", version);
goto fail;
}
/* get base size to simplify later parsing */
data->header_size = data->header_data_start;
if (data->header_sizes_start) {
for (i = 0; i < data->layer_max; i++) {
data->header_size += read_32bit(offset + data->header_sizes_start + i*0x04, streamfile);
}
}
/* force read header block */
data->logical_offset = -1;
/* just in case some headers may use less layers that stream has */
VGM_ASSERT(data->layer_count != data->layer_max, "UBI SB: non-matching layer counts\n");
if (data->layer_count > data->layer_max) {
VGM_LOG("UBI SB: layer count bigger than layer max\n");
goto fail;
}
/* Common layer quirks:
* - layer format depends on its own version and not on platform or DARE engine version
* - codec header may be in the layer header, or in the first block
* - stream size doesn't include padding
* - block number goes from 1 to block_count
* - block offset is relative to layer start
* - blocks data size varies between blocks and between layers in the same block
* - "config?" is a small value that varies between streams of the same game
* - next block size is 0 at last block
* - both Ubi SB and Ubi BAO use same-version layers
*/
return 1;
fail:
return 0;
}
/* Handles deinterleaving of Ubisoft's headered+blocked 'multitrack' streams */
static STREAMFILE* setup_ubi_sb_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, int layer_number, int layer_count, int big_endian) {
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
ubi_sb_io_data io_data = {0};
size_t io_data_size = sizeof(ubi_sb_io_data);
io_data.stream_offset = stream_offset;
io_data.stream_size = stream_size;
io_data.layer_number = layer_number;
io_data.layer_count = layer_count;
io_data.big_endian = big_endian;
if (!ubi_sb_io_init(streamFile, &io_data))
goto fail;
/* setup subfile */
new_streamFile = open_wrap_streamfile(streamFile);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, ubi_sb_io_read,ubi_sb_io_size);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
new_streamFile = open_buffer_streamfile(new_streamFile,0);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
return temp_streamFile;
fail:
close_streamfile(temp_streamFile);
return NULL;
}
#endif /* _UBI_SB_STREAMFILE_H_ */

View File

@ -22,7 +22,8 @@ VGMSTREAM * init_vgmstream_xnb(STREAMFILE *streamFile) {
platform = read_8bit(0x03,streamFile);
big_endian = (platform == 'x');
if (read_8bit(0x04,streamFile) != 0x05) /* XNA 4.0 version only */
if (read_8bit(0x04,streamFile) != 0x04 && /* XNA 3.0? found on Scare Me (XBLIG), no notable diffs */
read_8bit(0x04,streamFile) != 0x05) /* XNA 4.0 version */
goto fail;
flags = read_8bit(0x05,streamFile);

View File

@ -1199,6 +1199,7 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
case coding_MSADPCM:
return (vgmstream->interleave_block_size - 0x07*vgmstream->channels)*2 / vgmstream->channels + 2;
case coding_MSADPCM_int:
case coding_MSADPCM_ck:
return (vgmstream->interleave_block_size - 0x07)*2 + 2;
case coding_WS: /* only works if output sample size is 8 bit, which always is for WS ADPCM */
@ -1383,6 +1384,7 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
return 0x4c*vgmstream->channels;
case coding_MSADPCM:
case coding_MSADPCM_int:
case coding_MSADPCM_ck:
return vgmstream->interleave_block_size;
case coding_WS:
@ -1950,14 +1952,17 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
buffer+samples_written*vgmstream->channels, samples_to_do);
break;
case coding_MSADPCM:
if (vgmstream->channels == 2) {
case coding_MSADPCM_int:
if (vgmstream->channels == 1 || vgmstream->coding_type == coding_MSADPCM_int) {
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_msadpcm_mono(vgmstream,buffer+samples_written*vgmstream->channels+ch,
vgmstream->channels,vgmstream->samples_into_block, samples_to_do, ch);
}
}
else if (vgmstream->channels == 2) {
decode_msadpcm_stereo(vgmstream,buffer+samples_written*vgmstream->channels,
vgmstream->samples_into_block,samples_to_do);
}
else if (vgmstream->channels == 1) {
decode_msadpcm_mono(vgmstream,buffer+samples_written*vgmstream->channels,
vgmstream->channels,vgmstream->samples_into_block,samples_to_do, 0);
}
break;
case coding_MSADPCM_ck:
for (ch = 0; ch < vgmstream->channels; ch++) {
@ -2310,11 +2315,18 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
snprintf(temp,TEMPSIZE,
"\nlayout: ");
concatn(length,desc,temp);
description = get_vgmstream_layout_description(vgmstream->layout_type);
if (!description)
description = "INCONCEIVABLE";
switch (vgmstream->layout_type) {
case layout_layered:
snprintf(temp,TEMPSIZE,"%s (%i layers)",description, ((layered_layout_data*)vgmstream->layout_data)->layer_count);
break;
case layout_segmented:
snprintf(temp,TEMPSIZE,"%s (%i segments)",description, ((segmented_layout_data*)vgmstream->layout_data)->segment_count);
break;
default:
description = get_vgmstream_layout_description(vgmstream->layout_type);
if (!description)
description = "INCONCEIVABLE";
snprintf(temp,TEMPSIZE,"%s",description);
break;
}
@ -2342,6 +2354,7 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
if (vgmstream->layout_type == layout_none && vgmstream->interleave_block_size > 0) {
switch (vgmstream->coding_type) {
case coding_MSADPCM:
case coding_MSADPCM_int:
case coding_MSADPCM_ck:
case coding_MS_IMA:
case coding_MC3:
@ -2756,14 +2769,13 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s
if (!file) goto fail;
}
for (ch=0; ch < vgmstream->channels; ch++) {
for (ch = 0; ch < vgmstream->channels; ch++) {
off_t offset;
if (use_same_offset_per_channel) {
offset = start_offset;
} else if (is_stereo_codec) {
int ch_mod = (ch & 1) ? ch - 1 : ch; /* adjust odd channels (ch 0,1,2,3,4,5 > ch 0,0,2,2,4,4) */
offset = start_offset + vgmstream->interleave_block_size*ch_mod;
//VGM_LOG("ch%i offset=%lx\n", ch,offset);
} else {
offset = start_offset + vgmstream->interleave_block_size*ch;
}
@ -2774,6 +2786,7 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s
if (!file) goto fail;
}
VGM_LOG("ch%i offset=%lx\n", ch,offset);
vgmstream->ch[ch].streamfile = file;
vgmstream->ch[ch].channel_start_offset =
vgmstream->ch[ch].offset = offset;

View File

@ -137,6 +137,7 @@ typedef enum {
coding_H4M_IMA, /* H4M IMA ADPCM (stereo or mono, high nibble first) */
coding_MSADPCM, /* Microsoft ADPCM (stereo/mono) */
coding_MSADPCM_int, /* Microsoft ADPCM (mono) */
coding_MSADPCM_ck, /* Microsoft ADPCM (Cricket Audio variation) */
coding_WS, /* Westwood Studios VBR ADPCM */
coding_AICA, /* Yamaha AICA ADPCM (stereo) */