diff --git a/src/Makefile b/src/Makefile
index 7c41bb71..ddafdada 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -53,7 +53,8 @@ LAYOUT_OBJS=layout/ast_blocked.o \
layout/bdsp_blocked.o \
layout/tra_blocked.o \
layout/ps2_iab_blocked.o \
- layout/ps2_strlr_blocked.o
+ layout/ps2_strlr_blocked.o \
+ layout/scd_int_layout.o
META_OBJS=meta/adx_header.o \
meta/afc_header.o \
diff --git a/src/layout/Makefile.unix.am b/src/layout/Makefile.unix.am
index 673928a1..caa724d5 100644
--- a/src/layout/Makefile.unix.am
+++ b/src/layout/Makefile.unix.am
@@ -36,5 +36,6 @@ liblayout_la_SOURCES += bdsp_blocked.c
liblayout_la_SOURCES += tra_blocked.c
liblayout_la_SOURCES += ps2_iab_blocked.c
liblayout_la_SOURCES += ps2_strlr_blocked.c
+liblayout_la_SOURCES += scd_int_layout.c
EXTRA_DIST = layout.h
diff --git a/src/layout/layout.h b/src/layout/layout.h
index 5aa9c597..599e9353 100644
--- a/src/layout/layout.h
+++ b/src/layout/layout.h
@@ -58,6 +58,8 @@ void render_vgmstream_aix(sample * buffer, int32_t sample_count, VGMSTREAM * vgm
void render_vgmstream_aax(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
+void render_vgmstream_scd_int(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
+
void psx_mgav_block_update(off_t block_offset, VGMSTREAM * vgmstream);
void ps2_adm_block_update(off_t block_offset, VGMSTREAM * vgmstream);
diff --git a/src/layout/scd_int_layout.c b/src/layout/scd_int_layout.c
new file mode 100644
index 00000000..1a2c5e8a
--- /dev/null
+++ b/src/layout/scd_int_layout.c
@@ -0,0 +1,38 @@
+#include "layout.h"
+#include "../vgmstream.h"
+
+/* TODO: currently only properly handles mono substreams */
+/* TODO: there must be a reasonable way to respect the loop settings, as is
+ the substreams are in their own little world */
+
+#define INTERLEAVE_BUF_SIZE 512
+void render_vgmstream_scd_int(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
+ sample interleave_buf[INTERLEAVE_BUF_SIZE];
+ int32_t samples_done = 0;
+ scd_int_codec_data *data = vgmstream->codec_data;
+
+ while (samples_done < sample_count)
+ {
+ int32_t samples_to_do = INTERLEAVE_BUF_SIZE;
+ int c;
+ if (samples_to_do > sample_count - samples_done)
+ samples_to_do = sample_count - samples_done;
+
+ for (c=0; c < data->substream_count; c++)
+ {
+ int32_t i;
+
+ render_vgmstream(interleave_buf,
+ samples_to_do, data->substreams[c]);
+
+ for (i=0; i < samples_to_do; i++)
+ {
+ buffer[(samples_done+i)*data->substream_count + c] = interleave_buf[i];
+ }
+ }
+
+ samples_done += samples_to_do;
+
+ }
+}
+
diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj
index 997e76fc..06d63172 100644
--- a/src/libvgmstream.vcproj
+++ b/src/libvgmstream.vcproj
@@ -1366,6 +1366,10 @@
RelativePath=".\layout\psx_mgav_blocked.c"
>
+
+
diff --git a/src/meta/sqex_scd.c b/src/meta/sqex_scd.c
index 3bb03818..bd232495 100644
--- a/src/meta/sqex_scd.c
+++ b/src/meta/sqex_scd.c
@@ -3,6 +3,23 @@
#include "../util.h"
/* Square-Enix SCD (FF XIII, XIV) */
+
+/* special streamfile type to handle deinterleaving of complete files,
+ (based heavily on AIXSTREAMFILE */
+typedef struct _SCDINTSTREAMFILE
+{
+ STREAMFILE sf;
+ STREAMFILE *real_file;
+ const char * filename;
+ off_t start_physical_offset;
+ off_t current_logical_offset;
+ off_t interleave_block_size;
+ off_t stride_size;
+ size_t total_size;
+} SCDINTSTREAMFILE;
+
+static STREAMFILE *open_scdint_with_STREAMFILE(STREAMFILE *file, const char * filename, off_t start_offset, off_t interleave_block_size, off_t stride_size, size_t total_size);
+
VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[260];
@@ -193,8 +210,71 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
break;
case 0xA:
/* GC/Wii DSP ADPCM */
- /* TODO */
- goto fail;
+ {
+ STREAMFILE * file;
+ int i;
+ const off_t interleave_size = 0x800;
+ const off_t stride_size = interleave_size * channel_count;
+
+ size_t total_size;
+
+ scd_int_codec_data * data = NULL;
+
+ vgmstream->coding_type = coding_NGC_DSP;
+ vgmstream->layout_type = layout_scd_int;
+
+ /* a normal DSP header... */
+ vgmstream->num_samples = read_32bitBE(start_offset+0,streamFile);
+ total_size = (read_32bitBE(start_offset+4,streamFile)+1)/2;
+
+ if (loop_flag) {
+ vgmstream->loop_start_sample = loop_start;
+ vgmstream->loop_end_sample = loop_end+1;
+ }
+
+ /* verify other channel headers */
+ for (i = 1; i < channel_count; i++) {
+ if (read_32bitBE(start_offset+interleave_size*i+0,streamFile) != vgmstream->num_samples ||
+ (read_32bitBE(start_offset+4,streamFile)+1)/2 != total_size) {
+ goto fail;
+ }
+
+ }
+
+ /* the primary streamfile we'll be using */
+ file = streamFile->open(streamFile,filename,stride_size);
+ if (!file)
+ goto fail;
+
+ vgmstream->ch[0].streamfile = file;
+
+ data = malloc(sizeof(scd_int_codec_data));
+ data->substream_count = channel_count;
+ data->substreams = calloc(channel_count, sizeof(VGMSTREAM *));
+ data->intfiles = calloc(channel_count, sizeof(STREAMFILE *));
+
+ vgmstream->codec_data = data;
+
+ for (i=0;isubstreams[i] = init_vgmstream_ngc_dsp_std(intfile);
+ data->intfiles[i] = intfile;
+ if (!data->substreams[i])
+ goto fail;
+
+ /* TODO: only handles mono substreams, though that's all we have with DSP */
+ /* save start things so we can restart for seeking/looping */
+ /* copy the channels */
+ memcpy(data->substreams[i]->start_ch,data->substreams[i]->ch,sizeof(VGMSTREAMCHANNEL)*1);
+ /* copy the whole VGMSTREAM */
+ memcpy(data->substreams[i]->start_vgmstream,data->substreams[i],sizeof(VGMSTREAM));
+
+ }
+
+ }
+ break;
default:
goto fail;
}
@@ -202,6 +282,7 @@ VGMSTREAM * init_vgmstream_sqex_scd(STREAMFILE *streamFile) {
vgmstream->meta_type = meta_SQEX_SCD;
/* open the file for reading */
+ if (vgmstream->layout_type != layout_scd_int)
{
int i;
STREAMFILE * file;
@@ -223,3 +304,124 @@ fail:
if (vgmstream) close_vgmstream(vgmstream);
return NULL;
}
+
+static STREAMFILE *open_scdint_impl(SCDINTSTREAMFILE *streamfile,const char * const filename,size_t buffersize)
+{
+ SCDINTSTREAMFILE *newfile;
+
+ if (strcmp(filename, streamfile->filename))
+ return NULL;
+
+ newfile = malloc(sizeof(SCDINTSTREAMFILE));
+ if (!newfile)
+ return NULL;
+
+ memcpy(newfile,streamfile,sizeof(SCDINTSTREAMFILE));
+ return &newfile->sf;
+}
+
+static void close_scdint(SCDINTSTREAMFILE *streamfile)
+{
+ free(streamfile);
+ return;
+}
+
+static size_t get_size_scdint(SCDINTSTREAMFILE *streamfile)
+{
+ return streamfile->total_size;
+}
+
+static size_t get_offset_scdint(SCDINTSTREAMFILE *streamfile)
+{
+ return streamfile->current_logical_offset;
+}
+
+static void get_name_scdint(SCDINTSTREAMFILE *streamfile, char *buffer, size_t length)
+{
+ strncpy(buffer,streamfile->filename,length);
+ buffer[length-1]='\0';
+}
+
+static size_t read_scdint(SCDINTSTREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length)
+{
+ size_t sz = 0;
+
+ while (length > 0)
+ {
+ off_t to_read;
+ off_t length_available;
+ off_t block_num;
+ off_t intrablock_offset;
+ off_t physical_offset;
+
+
+ block_num = offset / streamfile->interleave_block_size;
+ intrablock_offset = offset % streamfile->interleave_block_size;
+ streamfile->current_logical_offset = offset;
+ physical_offset = streamfile->start_physical_offset + block_num * streamfile->stride_size + intrablock_offset;
+
+ length_available =
+ streamfile->interleave_block_size - intrablock_offset;
+
+ if (length < length_available)
+ {
+ to_read = length;
+ }
+ else
+ {
+ to_read = length_available;
+ }
+
+ if (to_read > 0)
+ {
+ size_t bytes_read;
+
+ bytes_read = read_streamfile(dest,
+ physical_offset,
+ to_read, streamfile->real_file);
+
+ sz += bytes_read;
+
+ streamfile->current_logical_offset = offset + bytes_read;
+
+ if (bytes_read != to_read)
+ {
+ /* an error which we will not attempt to handle here */
+ return sz;
+ }
+
+ dest += bytes_read;
+ offset += bytes_read;
+ length -= bytes_read;
+ }
+ }
+
+ return sz;
+}
+
+/* start_offset is for *this* interleaved stream */
+static STREAMFILE *open_scdint_with_STREAMFILE(STREAMFILE *file, const char * filename, off_t start_offset, off_t interleave_block_size, off_t stride_size, size_t total_size)
+{
+ SCDINTSTREAMFILE * scd = malloc(sizeof(SCDINTSTREAMFILE));
+
+ if (!scd)
+ return NULL;
+
+ scd->sf.read = (void*)read_scdint;
+ scd->sf.get_size = (void*)get_size_scdint;
+ scd->sf.get_offset = (void*)get_offset_scdint;
+ scd->sf.get_name = (void*)get_name_scdint;
+ scd->sf.get_realname = (void*)get_name_scdint;
+ scd->sf.open = (void*)open_scdint_impl;
+ scd->sf.close = (void*)close_scdint;
+
+ scd->real_file = file;
+ scd->filename = filename;
+ scd->start_physical_offset = start_offset;
+ scd->current_logical_offset = 0;
+ scd->interleave_block_size = interleave_block_size;
+ scd->stride_size = stride_size;
+ scd->total_size = total_size;
+
+ return &scd->sf;
+}
diff --git a/src/vgmstream.c b/src/vgmstream.c
index 759f0743..03235410 100644
--- a/src/vgmstream.c
+++ b/src/vgmstream.c
@@ -481,6 +481,17 @@ void reset_vgmstream(VGMSTREAM * vgmstream) {
nwa_codec_data *data = vgmstream->codec_data;
reset_nwa(data->nwa);
}
+
+ if (vgmstream->layout_type==layout_scd_int) {
+ scd_int_codec_data *data = vgmstream->codec_data;
+ int i;
+
+ for (i=0;isubstream_count;i++)
+ {
+ reset_vgmstream(data->substreams[i]);
+ }
+ }
+
}
/* simply allocate memory for the VGMSTREAM and its channels */
@@ -689,6 +700,29 @@ void close_vgmstream(VGMSTREAM * vgmstream) {
vgmstream->codec_data = NULL;
}
+ if (vgmstream->layout_type==layout_scd_int) {
+ scd_int_codec_data *data = vgmstream->codec_data;
+
+ if (data) {
+ if (data->substreams) {
+ int i;
+ for (i=0;isubstream_count;i++) {
+
+ /* note that the scd_int close_streamfile won't do anything
+ * but deallocate itself, there is only one open file and
+ * that is in vgmstream->ch[0].streamfile */
+ close_vgmstream(data->substreams[i]);
+ close_streamfile(data->intfiles[i]);
+ }
+ free(data->substreams);
+ free(data->intfiles);
+ }
+
+ free(data);
+ }
+ vgmstream->codec_data = NULL;
+ }
+
/* now that the special cases have had their chance, clean up the standard items */
for (i=0;ichannels;i++) {
if (vgmstream->ch[i].streamfile) {
@@ -779,6 +813,9 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre
case layout_aax:
render_vgmstream_aax(buffer,sample_count,vgmstream);
break;
+ case layout_scd_int:
+ render_vgmstream_scd_int(buffer,sample_count,vgmstream);
+ break;
}
}
@@ -1943,6 +1980,9 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
case layout_tra_blocked:
snprintf(temp,TEMPSIZE,"TRA blocked");
break;
+ case layout_scd_int:
+ snprintf(temp,TEMPSIZE,"SCD multistream interleave");
+ break;
default:
snprintf(temp,TEMPSIZE,"INCONCEIVABLE");
}
diff --git a/src/vgmstream.h b/src/vgmstream.h
index 3f00cf98..9ef4ddea 100644
--- a/src/vgmstream.h
+++ b/src/vgmstream.h
@@ -182,6 +182,7 @@ typedef enum {
layout_tra_blocked, /* DefJam Rapstar .tra blocks */
layout_ps2_iab_blocked,
layout_ps2_strlr_blocked,
+ layout_scd_int, /* deinterleave done by the SCDINTSTREAMFILE */
} layout_t;
/* The meta type specifies how we know what we know about the file. We may know because of a header we read, some of it may have been guessed from filenames, etc. */
@@ -741,6 +742,12 @@ typedef struct {
NWAData *nwa;
} nwa_codec_data;
+typedef struct {
+ int substream_count;
+ VGMSTREAM **substreams;
+ STREAMFILE **intfiles;
+} scd_int_codec_data;
+
/* do format detection, return pointer to a usable VGMSTREAM, or NULL on failure */
VGMSTREAM * init_vgmstream(const char * const filename);