diff --git a/src/formats.c b/src/formats.c
index 023b542e..cd1d88e5 100644
--- a/src/formats.c
+++ b/src/formats.c
@@ -1412,6 +1412,7 @@ static const meta_info meta_info_list[] = {
{meta_AWD, "RenderWare Audio Wave Dictionary header"},
{meta_SQUEAKSTREAM, "Torus SqueakStream header"},
{meta_SQUEAKSAMPLE, "Torus SqueakSample header"},
+ {meta_SNDS, "Sony SNDS header"},
};
void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) {
diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj
index adcd81b0..1171523c 100644
--- a/src/libvgmstream.vcxproj
+++ b/src/libvgmstream.vcxproj
@@ -628,6 +628,7 @@
+
diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters
index e136f555..af34e784 100644
--- a/src/libvgmstream.vcxproj.filters
+++ b/src/libvgmstream.vcxproj.filters
@@ -1705,6 +1705,9 @@
meta\Source Files
+
+ meta\Source Files
+
meta\Source Files
diff --git a/src/meta/meta.h b/src/meta/meta.h
index 93d52d6e..1bbc59e6 100644
--- a/src/meta/meta.h
+++ b/src/meta/meta.h
@@ -981,5 +981,6 @@ VGMSTREAM* init_vgmstream_pwb(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_squeakstream(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_squeaksample(STREAMFILE* sf);
+VGMSTREAM* init_vgmstream_snds(STREAMFILE* sf);
#endif /*_META_H*/
diff --git a/src/meta/snds.c b/src/meta/snds.c
new file mode 100644
index 00000000..bb5ec7c2
--- /dev/null
+++ b/src/meta/snds.c
@@ -0,0 +1,112 @@
+#include "meta.h"
+#include "../coding/coding.h"
+#include "../util/chunks.h"
+
+
+/* SSDD - Sony/SCE's SNDS lib format (cousin of SGXD/SNDX) */
+VGMSTREAM* init_vgmstream_snds(STREAMFILE* sf) {
+ VGMSTREAM* vgmstream = NULL;
+ uint32_t stream_offset, stream_size;
+ int loop_flag, channels, codec, sample_rate;
+ int32_t num_samples, loop_start, loop_end, encoder_delay;
+ uint32_t at9_config = 0;
+ int total_subsongs, target_subsong = sf->stream_index;
+
+
+ /* checks */
+ if (!is_id32be(0x00, sf, "SSDD"))
+ return NULL;
+
+ if (read_u32le(0x04, sf) != get_streamfile_size(sf))
+ return NULL;
+ /* 0x10: file name */
+
+ /* (extensionless): no apparent extension in debug strings, though comparing other SCE libs possibly ".ssd" */
+ if (!check_extensions(sf,""))
+ return NULL;
+
+ /* from debug info seems to have free chunks but known files always use the same and 1 subsong */
+ off_t base_offset = 0x60, wavs_offset = 0;
+ if (!find_chunk_le(sf, get_id32be("WAVS"),base_offset,0, &wavs_offset,NULL))
+ return NULL;
+
+ if (read_u16le(wavs_offset + 0x00, sf) != 0x2c) /* entry size? */
+ return NULL;
+
+ total_subsongs = read_s16le(wavs_offset + 0x02, sf);
+ if (target_subsong == 0) target_subsong = 1;
+ if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) return NULL;
+ if (total_subsongs != 1) return NULL; /* not seen */
+
+ /* read stream header */
+ {
+ uint32_t head_offset = wavs_offset + 0x04 + 0x2c * (target_subsong - 1);
+ /* 0x00: null/flags? */
+ /* 0x04: null/offset? */
+ /* 0x0c: null/offset? */
+ codec = read_u8(head_offset + 0x0c, sf);
+ channels = read_u16le(head_offset + 0x0d, sf);
+ /* 0x0e: null? */
+ sample_rate = read_u32le(head_offset + 0x10, sf);
+ at9_config = read_u32le(head_offset + 0x14, sf); /* !!! (only known use of this lib is Android/iOS) */
+ num_samples = read_s32le(head_offset + 0x18, sf);
+ loop_start = read_s32le(head_offset + 0x1c, sf);
+ loop_end = read_s32le(head_offset + 0x20, sf);
+ encoder_delay = read_s32le(head_offset + 0x24, sf);
+ stream_size = read_u32le(head_offset + 0x28, sf);
+
+ loop_flag = loop_end > 0;
+ }
+
+ /* CUES chunk: cues (various fields, also names) */
+ /* CUNS chunk: cue names (flags + hash + offset, then names) */
+
+ off_t wavd_offset = 0;
+ if (!find_chunk_le(sf, get_id32be("WAVD"),wavs_offset - 0x08,0, &wavd_offset,NULL))
+ return NULL;
+ stream_offset = wavd_offset + 0x08;
+
+
+ /* build the VGMSTREAM */
+ vgmstream = allocate_vgmstream(channels, loop_flag);
+ if (!vgmstream) goto fail;
+
+ vgmstream->meta_type = meta_SNDS;
+ vgmstream->sample_rate = sample_rate;
+ vgmstream->num_samples = num_samples;
+ vgmstream->loop_start_sample = loop_start;
+ vgmstream->loop_end_sample = loop_end;
+
+ vgmstream->num_streams = total_subsongs;
+ vgmstream->stream_size = stream_size;
+
+ switch (codec) {
+#ifdef VGM_USE_ATRAC9
+ case 0x41: {
+ atrac9_config cfg = {0};
+
+ cfg.channels = channels;
+ cfg.config_data = at9_config;
+ cfg.encoder_delay = encoder_delay;
+
+ vgmstream->codec_data = init_atrac9(&cfg);
+ if (!vgmstream->codec_data) goto fail;
+ vgmstream->coding_type = coding_ATRAC9;
+ vgmstream->layout_type = layout_none;
+ break;
+ }
+#endif
+ default:
+ VGM_LOG("SNDS: unknown codec 0x%x\n", codec);
+ goto fail;
+ }
+
+ /* open the file for reading */
+ if (!vgmstream_open_stream(vgmstream, sf, stream_offset))
+ goto fail;
+ return vgmstream;
+
+fail:
+ close_vgmstream(vgmstream);
+ return NULL;
+}
diff --git a/src/vgmstream.c b/src/vgmstream.c
index 6f7444b4..63d2e822 100644
--- a/src/vgmstream.c
+++ b/src/vgmstream.c
@@ -522,6 +522,7 @@ init_vgmstream_t init_vgmstream_functions[] = {
init_vgmstream_pwb,
init_vgmstream_squeakstream,
init_vgmstream_squeaksample,
+ init_vgmstream_snds,
/* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */
init_vgmstream_scd_pcm,
diff --git a/src/vgmstream_types.h b/src/vgmstream_types.h
index e25e0ad6..ca0f70f8 100644
--- a/src/vgmstream_types.h
+++ b/src/vgmstream_types.h
@@ -700,6 +700,7 @@ typedef enum {
meta_AWD,
meta_SQUEAKSTREAM,
meta_SQUEAKSAMPLE,
+ meta_SNDS,
} meta_t;