mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-12 09:40:51 +01:00
Merge pull request #355 from bnnm/ubi-mic-ue4msadpcm
ubi mic ue4msadpcm
This commit is contained in:
commit
199e786f94
@ -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)
|
||||
|
10
doc/TXTH.md
10
doc/TXTH.md
@ -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)
|
||||
|
@ -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"},
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
@ -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" */
|
||||
|
@ -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" */
|
||||
|
@ -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*);
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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) */
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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;
|
||||
|
2772
src/meta/ubi_sb.c
2772
src/meta/ubi_sb.c
File diff suppressed because it is too large
Load Diff
363
src/meta/ubi_sb_streamfile.h
Normal file
363
src/meta/ubi_sb_streamfile.h
Normal 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_ */
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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) */
|
||||
|
Loading…
Reference in New Issue
Block a user