diff --git a/src/formats.c b/src/formats.c
index 4f3ac938..54b6ec4f 100644
--- a/src/formats.c
+++ b/src/formats.c
@@ -1002,6 +1002,7 @@ static const meta_info meta_info_list[] = {
{meta_SMC_SMH, "Genki SMC+SMH header"},
{meta_OGG_YS8, "Ogg Vorbis (Ys VIII header)"},
{meta_PPST, "Parappa PPST header"},
+ {meta_OPUS_PPP, "AT9 OPUS header"},
#ifdef VGM_USE_FFMPEG
{meta_FFmpeg, "FFmpeg supported file format"},
diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj
index 59f24e6c..300f2fe5 100644
--- a/src/libvgmstream.vcproj
+++ b/src/libvgmstream.vcproj
@@ -737,6 +737,10 @@
+
+
+
diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters
index 741a060a..5db85d93 100644
--- a/src/libvgmstream.vcxproj.filters
+++ b/src/libvgmstream.vcxproj.filters
@@ -445,6 +445,9 @@
meta\Source Files
+
+ meta\Source Files
+
meta\Source Files
diff --git a/src/meta/meta.h b/src/meta/meta.h
index 1e8e48a0..6eed12da 100644
--- a/src/meta/meta.h
+++ b/src/meta/meta.h
@@ -742,4 +742,6 @@ VGMSTREAM * init_vgmstream_ea_sps_fb(STREAMFILE *streamFile);
VGMSTREAM * init_vgmstream_ppst(STREAMFILE *streamFile);
+VGMSTREAM * init_vgmstream_opus_ppp(STREAMFILE *streamFile);
+
#endif /*_META_H*/
diff --git a/src/meta/opus_ppp.c b/src/meta/opus_ppp.c
new file mode 100644
index 00000000..af764e94
--- /dev/null
+++ b/src/meta/opus_ppp.c
@@ -0,0 +1,120 @@
+#include "meta.h"
+#include "../coding/coding.h"
+#include "../layout/layout.h"
+
+static STREAMFILE* setup_opus_ppp_streamfile(STREAMFILE *streamFile, off_t subfile_offset, size_t subfile_size, const char* fake_ext);
+
+/* .AT9 Opus - from Penny-Punching Princess (Switch) */
+VGMSTREAM * init_vgmstream_opus_ppp(STREAMFILE *streamFile) {
+ VGMSTREAM * vgmstream = NULL;
+ off_t segment_offset;
+ int loop_flag, channel_count;
+ int i;
+
+ segmented_layout_data *data = NULL;
+ int segment_count, loop_start_segment = 0, loop_end_segment = 0;
+ int num_samples = 0, loop_start_sample = 0, loop_end_sample = 0;
+
+
+ /* checks */
+ if (!check_extensions(streamFile, "at9"))
+ goto fail;
+ if (read_32bitBE(0x00,streamFile) != 0x09000000) /* file type? DSPs had 08 */
+ goto fail;
+ if (read_32bitLE(0x04,streamFile) + 0x1c != get_streamfile_size(streamFile))
+ goto fail;
+ /* 0x08(2): sample rate, 0x0a(2): loop flag?, 0x0c: num_samples (slightly smaller than added samples) */
+
+ segment_count = 3; /* intro/loop/end */
+ loop_start_segment = 1;
+ loop_end_segment = 1;
+ loop_flag = (segment_count > 0);
+
+ /* init layout */
+ data = init_layout_segmented(segment_count);
+ if (!data) goto fail;
+
+ /* open each segment subfile */
+ segment_offset = 0x1c;
+ for (i = 0; i < segment_count; i++) {
+ STREAMFILE* temp_streamFile;
+ size_t segment_size = read_32bitLE(0x10+0x04*i,streamFile);
+
+ if (!segment_size)
+ goto fail;
+
+ temp_streamFile = setup_opus_ppp_streamfile(streamFile, segment_offset,segment_size, "opus");
+ if (!temp_streamFile) goto fail;
+
+ data->segments[i] = init_vgmstream_nsw_opus(temp_streamFile);
+ close_streamfile(temp_streamFile);
+ if (!data->segments[i]) goto fail;
+
+ segment_offset += segment_size;
+
+ //todo there are some trailing samples that must be removed for smooth loops, start skip seems ok
+ data->segments[i]->num_samples -= 374; //not correct for all files, no idea how to calculate
+
+ /* get looping and samples */
+ if (loop_flag && loop_start_segment == i)
+ loop_start_sample = num_samples;
+
+ num_samples += data->segments[i]->num_samples;
+
+ if (loop_flag && loop_end_segment == i)
+ loop_end_sample = num_samples;
+ }
+
+ /* setup segmented VGMSTREAMs */
+ if (!setup_layout_segmented(data))
+ goto fail;
+
+
+ channel_count = data->segments[0]->channels;
+
+ /* build the VGMSTREAM */
+ vgmstream = allocate_vgmstream(channel_count,loop_flag);
+ if (!vgmstream) goto fail;
+
+ vgmstream->sample_rate = (uint16_t)read_16bitLE(0x08,streamFile);
+ vgmstream->num_samples = num_samples;
+ vgmstream->loop_start_sample = loop_start_sample;
+ vgmstream->loop_end_sample = loop_end_sample;
+
+ vgmstream->meta_type = meta_OPUS_PPP;
+ vgmstream->coding_type = data->segments[0]->coding_type;
+ vgmstream->layout_type = layout_segmented;
+
+ vgmstream->layout_data = data;
+ data->loop_segment = loop_start_segment;
+
+ return vgmstream;
+
+fail:
+ close_vgmstream(vgmstream);
+ free_layout_segmented(data);
+ return NULL;
+}
+
+static STREAMFILE* setup_opus_ppp_streamfile(STREAMFILE *streamFile, off_t subfile_offset, size_t subfile_size, const char* fake_ext) {
+ STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
+
+ /* setup subfile */
+ new_streamFile = open_wrap_streamfile(streamFile);
+ if (!new_streamFile) goto fail;
+ temp_streamFile = new_streamFile;
+
+ new_streamFile = open_clamp_streamfile(temp_streamFile, subfile_offset,subfile_size);
+ if (!new_streamFile) goto fail;
+ temp_streamFile = new_streamFile;
+
+ new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,fake_ext);
+ if (!new_streamFile) goto fail;
+ temp_streamFile = new_streamFile;
+
+ return temp_streamFile;
+
+fail:
+ close_streamfile(temp_streamFile);
+ return NULL;
+}
diff --git a/src/vgmstream.c b/src/vgmstream.c
index 6cd5f2b6..52fc9bac 100644
--- a/src/vgmstream.c
+++ b/src/vgmstream.c
@@ -401,6 +401,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_smc_smh,
init_vgmstream_ea_sps_fb,
init_vgmstream_ppst,
+ init_vgmstream_opus_ppp,
init_vgmstream_txth, /* should go at the end (lower priority) */
#ifdef VGM_USE_FFMPEG
diff --git a/src/vgmstream.h b/src/vgmstream.h
index 29aaa820..851e2527 100644
--- a/src/vgmstream.h
+++ b/src/vgmstream.h
@@ -672,6 +672,7 @@ typedef enum {
meta_SMC_SMH, /* Wangan Midnight (System 246) */
meta_OGG_YS8, /* Ogg Vorbis with encryption (Ys VIII PC) */
meta_PPST, /* PPST [Parappa the Rapper (PSP)] */
+ meta_OPUS_PPP, /* .at9 Opus [Penny-Punching Princess (Switch)] */
#ifdef VGM_USE_FFMPEG
meta_FFmpeg,