Add XMD decoder [Silent Hill 4, Castlevania: Curse of Darkness (Xbox)

This commit is contained in:
bnnm 2018-07-27 17:11:11 +02:00
parent b43063f8a2
commit 61034484ab
10 changed files with 170 additions and 0 deletions

View File

@ -151,6 +151,9 @@ void decode_fadpcm(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacin
/* asf_decoder */
void decode_asf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
/* xmd_decoder */
void decode_xmd(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size);
/* ea_mt_decoder*/
ea_mt_codec_data *init_ea_mt(int channel_count, int type);
void decode_ea_mt(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);

63
src/coding/xmd_decoder.c Normal file
View File

@ -0,0 +1,63 @@
#include "coding.h"
/* Decodes Konami XMD from Xbox games (algorithm info from xmd2wav/xmddecode.dll). */
void decode_xmd(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) {
off_t frame_offset;
int i, frames_in, sample_count = 0, samples_done = 0;
size_t bytes_per_frame, samples_per_frame;
int16_t hist1, hist2;
uint16_t scale;
/* external interleave (variable size), mono */
bytes_per_frame = frame_size;
samples_per_frame = 2 + (frame_size - 0x06) * 2;
frames_in = first_sample / samples_per_frame;
first_sample = first_sample % samples_per_frame;
/* parse frame header */
frame_offset = stream->offset + bytes_per_frame*frames_in;
hist2 = read_16bitLE(frame_offset+0x00,stream->streamfile);
hist1 = read_16bitLE(frame_offset+0x02,stream->streamfile);
scale = (uint16_t)read_16bitLE(frame_offset+0x04,stream->streamfile); /* scale doesn't go too high though */
/* write header samples (needed) */
if (sample_count >= first_sample && samples_done < samples_to_do) {
outbuf[samples_done * channelspacing] = hist2;
samples_done++;
}
sample_count++;
if (sample_count >= first_sample && samples_done < samples_to_do) {
outbuf[samples_done * channelspacing] = hist1;
samples_done++;
}
sample_count++;
/* decode nibbles */
for (i = first_sample; i < first_sample + samples_to_do; i++) {
int32_t new_sample;
uint8_t nibbles = (uint8_t)read_8bit(frame_offset+0x06 + i/2,stream->streamfile);
new_sample = i&1 ? /* low nibble first */
get_high_nibble_signed(nibbles):
get_low_nibble_signed(nibbles);
new_sample = (new_sample*(scale<<14) + (hist1*0x7298) - (hist2*0x3350)) >> 14;
/* Coefs are similar to XA's filter 2, but using those creates hissing in some songs.
* ex. 1.796875 * (1 << 14) = 0x7300, -0.8125 * (1 << 14) = -0x3400 */
//new_sample = (int32_t)(new_sample*scale + hist1*1.796875 + hist2*-0.8125);
//new_sample = clamp16(new_sample); /* not needed */
if (sample_count >= first_sample && samples_done < samples_to_do) {
outbuf[samples_done * channelspacing] = (int16_t)new_sample;
samples_done++;
}
sample_count++;
hist2 = hist1;
hist1 = new_sample;
}
//stream->adpcm_history1_32 = hist1;
//stream->adpcm_history2_32 = hist2;
}

View File

@ -437,6 +437,7 @@ static const char* extension_list[] = {
"xvas",
"xwav",//fake, to be removed
"xwb",
"xmd",
"xwc",
"xwm", //FFmpeg, not parsed (XWMA)
"xwma", //FFmpeg, not parsed (XWMA)
@ -555,6 +556,7 @@ static const coding_info coding_info_list[] = {
{coding_MC3, "Paradigm MC3 3-bit ADPCM"},
{coding_FADPCM, "FMOD FADPCM 4-bit ADPCM"},
{coding_ASF, "Argonaut ASF 4-bit ADPCM"},
{coding_XMD, "Konami XMD 4-bit ADPCM"},
{coding_SDX2, "Squareroot-delta-exact (SDX2) 8-bit DPCM"},
{coding_SDX2_int, "Squareroot-delta-exact (SDX2) 8-bit DPCM with 1 byte interleave"},
@ -1041,6 +1043,7 @@ static const meta_info meta_info_list[] = {
{meta_H4M, "Hudson HVQM4 header"},
{meta_OGG_MUS, "Ogg Vorbis (MUS header)"},
{meta_ASF, "Argonaut ASF header"},
{meta_XMD, "Konami XMD header"},
#ifdef VGM_USE_FFMPEG
{meta_FFmpeg, "FFmpeg supported file format"},

View File

@ -1473,6 +1473,10 @@
<File
RelativePath=".\meta\xvag.c"
>
</File>
<File
RelativePath=".\meta\xmd.c"
>
</File>
<File
RelativePath=".\meta\xwb.c"
@ -1714,6 +1718,10 @@
RelativePath=".\coding\xa_decoder.c"
>
</File>
<File
RelativePath=".\coding\xmd_decoder.c"
>
</File>
<File
RelativePath=".\coding\vorbis_custom_decoder.c"
>

View File

@ -442,6 +442,7 @@
<ClCompile Include="meta\xnb.c" />
<ClCompile Include="meta\xss.c" />
<ClCompile Include="meta\xvag.c" />
<ClCompile Include="meta\xmd.c" />
<ClCompile Include="meta\xwb.c" />
<ClCompile Include="meta\xwc.c" />
<ClCompile Include="meta\ydsp.c" />
@ -487,6 +488,7 @@
<ClCompile Include="coding\vorbis_custom_utils_wwise.c" />
<ClCompile Include="coding\ws_decoder.c" />
<ClCompile Include="coding\xa_decoder.c" />
<ClCompile Include="coding\xmd_decoder.c" />
<ClCompile Include="layout\segmented.c" />
<ClCompile Include="layout\aix_layout.c" />
<ClCompile Include="layout\blocked_ast.c" />

View File

@ -895,6 +895,9 @@
<ClCompile Include="meta\xvag.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\xmd.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
<ClCompile Include="meta\xwb.c">
<Filter>meta\Source Files</Filter>
</ClCompile>
@ -1027,6 +1030,9 @@
<ClCompile Include="coding\xa_decoder.c">
<Filter>coding\Source Files</Filter>
</ClCompile>
<ClCompile Include="coding\xmd_decoder.c">
<Filter>coding\Source Files</Filter>
</ClCompile>
<ClCompile Include="layout\segmented.c">
<Filter>layout\Source Files</Filter>
</ClCompile>

View File

@ -765,4 +765,6 @@ VGMSTREAM * init_vgmstream_h4m(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_asf(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_xmd(STREAMFILE *streamFile);
#endif /*_META_H*/

69
src/meta/xmd.c Normal file
View File

@ -0,0 +1,69 @@
#include "meta.h"
#include "../coding/coding.h"
/* XMD - from Konami Xbox games [Silent Hill 4 (Xbox), Castlevania Curse of Darkness (Xbox)] */
VGMSTREAM * init_vgmstream_xmd(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
int loop_flag, channel_count, sample_rate;
size_t data_size, loop_start, frame_size;
/* checks (.xmd comes from bigfiles with filenames) */
if (!check_extensions(streamFile, "xmd"))
goto fail;
if ((read_32bitBE(0x00,streamFile) & 0xFFFFFF00) == 0x786D6400) { /* "xmd\0" */
/* v2 from Castlevania: Curse of Darkness */
channel_count = read_8bit(0x03,streamFile);
sample_rate = (uint16_t)read_16bitLE(0x04, streamFile);
data_size = read_32bitLE(0x06, streamFile);
loop_flag = read_8bit(0x0a,streamFile);
loop_start = read_32bitLE(0x0b, streamFile);
/* 0x0f(2): unknown+config? */
frame_size = 0x15;
start_offset = 0x11;
}
else {
/* v1 from Silent Hill 4 */
channel_count = read_8bit(0x00,streamFile);
sample_rate = (uint16_t)read_16bitLE(0x01, streamFile);
data_size = read_32bitLE(0x03, streamFile);
loop_flag = read_8bit(0x07,streamFile);
loop_start = read_32bitLE(0x08, streamFile);
frame_size = 0x0d;
start_offset = 0x0c;
}
/* extra checks just in case */
if (data_size > get_streamfile_size(streamFile))
goto fail; /* v1 .xmd are sector-aligned with padding */
if (channel_count > 2)
goto fail;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count, loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = data_size / frame_size / channel_count * ((frame_size-0x06)*2 + 2); /* bytes to samples */
if (loop_flag) {
vgmstream->loop_start_sample = loop_start / frame_size / channel_count * ((frame_size-0x06)*2 + 2); /* bytes to samples */
vgmstream->loop_end_sample = vgmstream->num_samples;
}
vgmstream->meta_type = meta_XMD;
vgmstream->coding_type = coding_XMD;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = frame_size;
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail;
return vgmstream;
fail:
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -419,6 +419,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_h4m,
init_vgmstream_ps2_ads_container,
init_vgmstream_asf,
init_vgmstream_xmd,
init_vgmstream_txth, /* should go at the end (lower priority) */
#ifdef VGM_USE_FFMPEG
@ -1147,6 +1148,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
return 256; /* (0x8c - 0xc) * 2 */
case coding_ASF:
return 32; /* (0x11 - 0x1) * 2 */
case coding_XMD:
return (vgmstream->interleave_block_size - 0x06)*2 + 2;
case coding_EA_MT:
return 432;
case coding_CRI_HCA:
@ -1305,6 +1308,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
return 0x8c;
case coding_ASF:
return 0x11;
case coding_XMD:
return vgmstream->interleave_block_size;
case coding_EA_MT:
return 0; /* variable (frames of bit counts or PCM frames) */
#ifdef VGM_USE_ATRAC9
@ -1943,6 +1948,13 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
samples_to_do);
}
break;
case coding_XMD:
for (chan=0;chan<vgmstream->channels;chan++) {
decode_xmd(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,
vgmstream->channels,vgmstream->samples_into_block,
samples_to_do, vgmstream->interleave_block_size);
}
break;
case coding_EA_MT:
for (chan=0;chan<vgmstream->channels;chan++) {
decode_ea_mt(vgmstream, buffer+samples_written*vgmstream->channels+chan,

View File

@ -156,6 +156,7 @@ typedef enum {
coding_MC3, /* Paradigm MC3 3-bit ADPCM */
coding_FADPCM, /* FMOD FADPCM 4-bit ADPCM */
coding_ASF, /* Argonaut ASF 4-bit ADPCM */
coding_XMD, /* Konami XMD 4-bit ADPCM */
/* others */
coding_SDX2, /* SDX2 2:1 Squareroot-Delta-Exact compression DPCM */
@ -683,6 +684,7 @@ typedef enum {
meta_H4M, /* Hudson HVQM4 video [Resident Evil 0 (GC), Tales of Symphonia (GC)] */
meta_OGG_MUS, /* Ogg Vorbis with encryption [Redux - Dark Matters (PC)] */
meta_ASF, /* Argonaut ASF [Croc 2 (PC)] */
meta_XMD, /* Konami XMD [Silent Hill 4 (Xbox), Castlevania: Curse of Darkness (Xbox)] */
#ifdef VGM_USE_FFMPEG
meta_FFmpeg,