diff --git a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj
index 37ded1057..fe0ed4f17 100644
--- a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj
+++ b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj
@@ -401,7 +401,6 @@
 		836F703018BDC2190095E648 /* sqex_scd.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EF418BDC2190095E648 /* sqex_scd.c */; };
 		836F703218BDC2190095E648 /* str_asr.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EF618BDC2190095E648 /* str_asr.c */; };
 		836F703318BDC2190095E648 /* str_snds.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EF718BDC2190095E648 /* str_snds.c */; };
-		836F703418BDC2190095E648 /* stx.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EF818BDC2190095E648 /* stx.c */; };
 		836F703518BDC2190095E648 /* svs.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EF918BDC2190095E648 /* svs.c */; };
 		836F703618BDC2190095E648 /* thp.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EFA18BDC2190095E648 /* thp.c */; };
 		836F703718BDC2190095E648 /* tun.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EFB18BDC2190095E648 /* tun.c */; };
@@ -522,6 +521,9 @@
 		83AA5D251F6E2F9C0020821C /* hca_keys.h in Headers */ = {isa = PBXBuildFile; fileRef = 83AA5D211F6E2F9C0020821C /* hca_keys.h */; };
 		83AA5D271F6E2F9C0020821C /* stm.c in Sources */ = {isa = PBXBuildFile; fileRef = 83AA5D231F6E2F9C0020821C /* stm.c */; };
 		83AB8C761E8072A100086084 /* x360_ast.c in Sources */ = {isa = PBXBuildFile; fileRef = 83AB8C741E8072A100086084 /* x360_ast.c */; };
+		83AFABBC23795202002F3947 /* xssb.c in Sources */ = {isa = PBXBuildFile; fileRef = 83AFABB923795201002F3947 /* xssb.c */; };
+		83AFABBD23795202002F3947 /* ea_eaac_opus_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 83AFABBA23795202002F3947 /* ea_eaac_opus_streamfile.h */; };
+		83AFABBE23795202002F3947 /* isb.c in Sources */ = {isa = PBXBuildFile; fileRef = 83AFABBB23795202002F3947 /* isb.c */; };
 		83BAFB6C19F45EB3005DAB60 /* bfstm.c in Sources */ = {isa = PBXBuildFile; fileRef = 83BAFB6B19F45EB3005DAB60 /* bfstm.c */; };
 		83C7280F22BC893D00678B4A /* xwb_xsb.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C727FB22BC893800678B4A /* xwb_xsb.h */; };
 		83C7281022BC893D00678B4A /* nps.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C727FC22BC893900678B4A /* nps.c */; };
@@ -1087,7 +1089,6 @@
 		836F6EF418BDC2190095E648 /* sqex_scd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sqex_scd.c; sourceTree = "<group>"; };
 		836F6EF618BDC2190095E648 /* str_asr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = str_asr.c; sourceTree = "<group>"; };
 		836F6EF718BDC2190095E648 /* str_snds.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = str_snds.c; sourceTree = "<group>"; };
-		836F6EF818BDC2190095E648 /* stx.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = stx.c; sourceTree = "<group>"; };
 		836F6EF918BDC2190095E648 /* svs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = svs.c; sourceTree = "<group>"; };
 		836F6EFA18BDC2190095E648 /* thp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = thp.c; sourceTree = "<group>"; };
 		836F6EFB18BDC2190095E648 /* tun.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = tun.c; sourceTree = "<group>"; };
@@ -1207,6 +1208,9 @@
 		83AA5D211F6E2F9C0020821C /* hca_keys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hca_keys.h; sourceTree = "<group>"; };
 		83AA5D231F6E2F9C0020821C /* stm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = stm.c; sourceTree = "<group>"; };
 		83AB8C741E8072A100086084 /* x360_ast.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x360_ast.c; sourceTree = "<group>"; };
+		83AFABB923795201002F3947 /* xssb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xssb.c; sourceTree = "<group>"; };
+		83AFABBA23795202002F3947 /* ea_eaac_opus_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ea_eaac_opus_streamfile.h; sourceTree = "<group>"; };
+		83AFABBB23795202002F3947 /* isb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = isb.c; sourceTree = "<group>"; };
 		83BAFB6B19F45EB3005DAB60 /* bfstm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bfstm.c; sourceTree = "<group>"; };
 		83C727FB22BC893800678B4A /* xwb_xsb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xwb_xsb.h; sourceTree = "<group>"; };
 		83C727FC22BC893900678B4A /* nps.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = nps.c; sourceTree = "<group>"; };
@@ -1600,6 +1604,7 @@
 				83299FCF1E7660C7003A3242 /* dsp_adx.c */,
 				836F6E4418BDC2180095E648 /* dsp_bdsp.c */,
 				8349A8FF1FE6258000E26435 /* ea_1snh.c */,
+				83AFABBA23795202002F3947 /* ea_eaac_opus_streamfile.h */,
 				8306B0BD2098458B000302D4 /* ea_eaac_streamfile.h */,
 				8349A8F71FE6257E00E26435 /* ea_eaac.c */,
 				830165981F256BD000CA0941 /* ea_schl_fixed.c */,
@@ -1642,6 +1647,7 @@
 				837CEAE623487F2B00E62A4A /* ima.c */,
 				832BF81121E05149006F50F1 /* imc.c */,
 				836F6E5518BDC2180095E648 /* ios_psnd.c */,
+				83AFABBB23795202002F3947 /* isb.c */,
 				836F6E5618BDC2180095E648 /* ish_isd.c */,
 				836F6E5718BDC2180095E648 /* ivaud.c */,
 				836F6E5818BDC2180095E648 /* ivb.c */,
@@ -1849,7 +1855,6 @@
 				836F6EF718BDC2190095E648 /* str_snds.c */,
 				834FE0C2215C79E6000A5D3D /* str_wav.c */,
 				83C7280722BC893B00678B4A /* strm_abylight.c */,
-				836F6EF818BDC2190095E648 /* stx.c */,
 				834FE0D7215C79EA000A5D3D /* svg.c */,
 				836F6EF918BDC2190095E648 /* svs.c */,
 				831BA6121EAC61A500CF89B0 /* sxd.c */,
@@ -1923,6 +1928,7 @@
 				832BF80A21E05148006F50F1 /* xpcm.c */,
 				832BF80C21E05148006F50F1 /* xps.c */,
 				836F6F1218BDC2190095E648 /* xss.c */,
+				83AFABB923795201002F3947 /* xssb.c */,
 				834FE0C6215C79E7000A5D3D /* xvag_streamfile.h */,
 				83345A4E1F8AEB2800B2EAA4 /* xvag.c */,
 				837CEADC23487F2900E62A4A /* xvas.c */,
@@ -2023,6 +2029,7 @@
 				8306B0D820984590000302D4 /* ea_eaac_streamfile.h in Headers */,
 				837CEB0523487F2C00E62A4A /* sqex_sead_streamfile.h in Headers */,
 				8306B0E820984590000302D4 /* opus_interleave_streamfile.h in Headers */,
+				83AFABBD23795202002F3947 /* ea_eaac_opus_streamfile.h in Headers */,
 				83C7281822BC893D00678B4A /* sfh_streamfile.h in Headers */,
 				834FE0EF215C79ED000A5D3D /* xvag_streamfile.h in Headers */,
 				8349A90C1FE6258200E26435 /* sqex_scd_streamfile.h in Headers */,
@@ -2249,7 +2256,6 @@
 				836F6FA118BDC2190095E648 /* myspd.c in Sources */,
 				837CEB0123487F2C00E62A4A /* xmu.c in Sources */,
 				836F6FD718BDC2190095E648 /* ps2_filp.c in Sources */,
-				836F703418BDC2190095E648 /* stx.c in Sources */,
 				83FF0EBC1E93282100C58054 /* wwise.c in Sources */,
 				836F6F7018BDC2190095E648 /* apple_caff.c in Sources */,
 				836F700018BDC2190095E648 /* ps2_svag.c in Sources */,
@@ -2295,6 +2301,7 @@
 				8349A8EA1FE6253900E26435 /* blocked_ea_schl.c in Sources */,
 				836F705118BDC2190095E648 /* zsd.c in Sources */,
 				8349A91F1FE6258200E26435 /* naac.c in Sources */,
+				83AFABBE23795202002F3947 /* isb.c in Sources */,
 				836F6FD218BDC2190095E648 /* ps2_bmdx.c in Sources */,
 				836F703C18BDC2190095E648 /* wii_bns.c in Sources */,
 				830EBE132004656E0023AA10 /* xnb.c in Sources */,
@@ -2594,6 +2601,7 @@
 				83C7282922BC8C1500678B4A /* mixing.c in Sources */,
 				8375737621F950ED00F01AF5 /* gin.c in Sources */,
 				836F6FC918BDC2190095E648 /* ps2_2pfs.c in Sources */,
+				83AFABBC23795202002F3947 /* xssb.c in Sources */,
 				836F704818BDC2190095E648 /* xbox_ims.c in Sources */,
 				837CEAF623487F2C00E62A4A /* mzrt.c in Sources */,
 				836F6F7518BDC2190095E648 /* bnsf.c in Sources */,
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/asf_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/asf_decoder.c
index c0db63018..dec827cfa 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/asf_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/asf_decoder.c
@@ -2,52 +2,53 @@
 
 
 /* Decodes Argonaut's ASF ADPCM codec, used in some of their PC games.
- * Algorithm should be accurate (reverse engineered from asfcodec.adl DLL). */
-void decode_asf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
+ * Reverse engineered from asfcodec.adl DLL. */
+void decode_asf(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
+    uint8_t frame[0x11] = {0};
     off_t frame_offset;
     int i, frames_in, sample_count = 0;
     size_t bytes_per_frame, samples_per_frame;
-    uint8_t shift, mode;
+    int shift, mode;
     int32_t hist1 = stream->adpcm_history1_32;
     int32_t hist2 = stream->adpcm_history2_32;
 
+
     /* external interleave (fixed size), mono */
     bytes_per_frame = 0x11;
     samples_per_frame = (bytes_per_frame - 0x01) * 2;
     frames_in = first_sample / samples_per_frame;
-    first_sample = first_sample % samples_per_frame;
+    //first_sample = first_sample % samples_per_frame; /* for flat layout */
 
     /* parse frame header */
     frame_offset = stream->offset + bytes_per_frame*frames_in;
-    shift = ((uint8_t)read_8bit(frame_offset+0x00,stream->streamfile) >> 4) & 0xf;
-    mode  = ((uint8_t)read_8bit(frame_offset+0x00,stream->streamfile) >> 0) & 0xf;
+    read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
+    shift = (frame[0x00] >> 4) & 0xf;
+    mode  = (frame[0x00] >> 0) & 0xf;
 
     /* 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+0x01 + i/2,stream->streamfile);
+        uint8_t nibbles = frame[0x01 + i/2];
+        int32_t sample;
 
-        new_sample = i&1 ? /* high nibble first */
+        sample = i&1 ? /* high nibble first */
                 get_low_nibble_signed(nibbles):
                 get_high_nibble_signed(nibbles);
-        /* move sample to upper nibble, then shift + 2 (IOW: shift + 6) */
-        new_sample = (new_sample << 4) << (shift + 2);
+        sample = (sample << 4) << (shift + 2); /* move sample to upper nibble, then shift + 2 (IOW: shift + 6) */
 
         /* mode is checked as a flag, so there are 2 modes only, but lower nibble
          * may have other values at last frame (ex 0x02/09), could be control flags (loop related?) */
         if (mode & 0x4) { /* ~filters: 2, -1  */
-            new_sample = (new_sample + (hist1 << 7) - (hist2 << 6)) >> 6;
+            sample = (sample + (hist1 << 7) - (hist2 << 6)) >> 6;
         }
         else { /* ~filters: 1, 0  */
-            new_sample = (new_sample + (hist1 << 6)) >> 6;
+            sample = (sample + (hist1 << 6)) >> 6;
         }
 
-        //new_sample = clamp16(new_sample); /* must not */
-        outbuf[sample_count] = (int16_t)new_sample;
+        outbuf[sample_count] = (int16_t)sample; /* must not clamp */
         sample_count += channelspacing;
 
         hist2 = hist1;
-        hist1 = new_sample;
+        hist1 = sample;
     }
 
     stream->adpcm_history1_32 = hist1;
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/coding.h b/Frameworks/vgmstream/vgmstream/src/coding/coding.h
index 18aaf3846..ef9db19b7 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/coding.h
+++ b/Frameworks/vgmstream/vgmstream/src/coding/coding.h
@@ -55,10 +55,10 @@ void dsp_read_hist_le(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t offse
 void dsp_read_hist(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t offset, off_t spacing, int be);
 
 /* ngc_dtk_decoder */
-void decode_ngc_dtk(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
+void decode_ngc_dtk(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
 
 /* ngc_afc_decoder */
-void decode_ngc_afc(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
+void decode_ngc_afc(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
 
 /* pcm_decoder */
 void decode_pcm16le(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
@@ -138,7 +138,7 @@ size_t yamaha_bytes_to_samples(size_t bytes, int channels);
 size_t aska_bytes_to_samples(size_t bytes, int channels);
 
 /* nds_procyon_decoder */
-void decode_nds_procyon(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
+void decode_nds_procyon(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
 
 /* l5_555_decoder */
 void decode_l5_555(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
@@ -147,28 +147,28 @@ void decode_l5_555(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacin
 void decode_sassc(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
 
 /* lsf_decode */
-void decode_lsf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
+void decode_lsf(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
 
 /* mtaf_decoder */
-void decode_mtaf(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
+void decode_mtaf(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
 
 /* mta2_decoder */
-void decode_mta2(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
+void decode_mta2(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
 
 /* mc3_decoder */
 void decode_mc3(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
 
 /* fadpcm_decoder */
-void decode_fadpcm(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
+void decode_fadpcm(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
 
 /* asf_decoder */
-void decode_asf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
+void decode_asf(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
 
 /* dsa_decoder */
-void decode_dsa(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
+void decode_dsa(VGMSTREAMCHANNEL *stream, sample_t *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);
+void decode_xmd(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size);
 
 /* derf_decoder */
 void decode_derf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
@@ -183,7 +183,7 @@ void decode_oki4s(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspaci
 size_t oki_bytes_to_samples(size_t bytes, int channels);
 
 /* ptadpcm_decoder */
-void decode_ptadpcm(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size);
+void decode_ptadpcm(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size);
 size_t ptadpcm_bytes_to_samples(size_t bytes, int channels, size_t frame_size);
 
 /* ubi_adpcm_decoder */
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c
index 74e256128..f5b0c883d 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c
@@ -10,37 +10,38 @@ static const int dsa_coefs[16] = {
 
 /* Decodes Ocean DSA ADPCM codec from Last Rites (PC).
  * Reverse engineered from daemon1's reverse engineering. */
-void decode_dsa(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
+void decode_dsa(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
+    uint8_t frame[0x08] = {0};
     off_t frame_offset;
     int i, frames_in, sample_count = 0;
     size_t bytes_per_frame, samples_per_frame;
-    uint8_t header;
-    int shift, filter;
+    int index, shift, coef;
     int32_t hist1 = stream->adpcm_history1_32;
 
+
     /* external interleave (fixed size), mono */
     bytes_per_frame = 0x08;
     samples_per_frame = (bytes_per_frame - 0x01) * 2;
     frames_in = first_sample / samples_per_frame;
-    first_sample = first_sample % samples_per_frame;
+    first_sample = first_sample % samples_per_frame; /* for flat layout */
 
     /* parse frame header */
-    frame_offset = stream->offset + bytes_per_frame*frames_in;
-    header = (uint8_t)read_8bit(frame_offset+0x00,stream->streamfile);
-    shift  = 0x0c - ((header >> 4) & 0xf);
-    filter = dsa_coefs[header & 0xf];
+    frame_offset = stream->offset + bytes_per_frame * frames_in;
+    read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
+    index = ((frame[0] >> 0) & 0xf);
+    shift = 12 - ((frame[0] >> 4) & 0xf);
+    coef = dsa_coefs[index];
 
     /* decode nibbles */
     for (i = first_sample; i < first_sample + samples_to_do; i++) {
+        uint8_t nibbles = frame[0x01 + i/2];
         int32_t sample;
-        uint8_t nibbles = (uint8_t)read_8bit(frame_offset+0x01 + i/2,stream->streamfile);
 
         sample = i&1 ? /* high nibble first */
                 (nibbles >> 0) & 0xf :
                 (nibbles >> 4) & 0xf;
-
-        sample = ((int16_t)(sample << 0xC) >> shift); /* 16b sign extend + scale */
-        sample = sample + ((hist1 * filter) >> 0x10);
+        sample = ((int16_t)(sample << 12) >> shift); /* 16b sign extend + scale */
+        sample = sample + ((hist1 * coef) >> 16);
 
         outbuf[sample_count] = (sample_t)(sample << 2);
         sample_count += channelspacing;
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/fadpcm_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/fadpcm_decoder.c
index e70f6adc9..a52a1437d 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/fadpcm_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/fadpcm_decoder.c
@@ -1,77 +1,74 @@
 #include "coding.h"
 
 
-/* FADPCM table */
+/* tweaked XA/PSX coefs << 6 */
 static const int8_t fadpcm_coefs[8][2] = {
-        {   0 ,   0 },
-        {  60 ,   0 },
-        { 122 ,  60 },
-        { 115 ,  52 },
-        {  98 ,  55 },
-        {   0 ,   0 },
-        {   0 ,   0 },
-        {   0 ,   0 },
+        {   0,   0 },
+        {  60,   0 },
+        { 122,  60 },
+        { 115,  52 },
+        {  98,  55 },
+        /* rest is 0s */
 };
 
 /* FMOD's FADPCM, basically XA/PSX ADPCM with a fancy header layout.
  * Code/layout could be simplified but tries to emulate FMOD's code.
  * Algorithm and tables debugged from their PC DLLs (byte-accurate). */
-void decode_fadpcm(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
+void decode_fadpcm(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
+    uint8_t frame[0x8c] = {0};
     off_t frame_offset;
-    int i, j, k;
-    int block_samples, frames_in, samples_done = 0, sample_count = 0;
+    int i, j, k, frames_in, sample_count = 0, samples_done = 0;
+    size_t bytes_per_frame, samples_per_frame;
     uint32_t coefs, shifts;
     int32_t hist1; //= stream->adpcm_history1_32;
     int32_t hist2; //= stream->adpcm_history2_32;
 
     /* external interleave (fixed size), mono */
-    block_samples = (0x8c - 0xc) * 2;
-    frames_in = first_sample / block_samples;
-    first_sample = first_sample % block_samples;
-
-    frame_offset = stream->offset + 0x8c*frames_in;
-
+    bytes_per_frame = 0x8c;
+    samples_per_frame = (bytes_per_frame - 0xc) * 2;
+    frames_in = first_sample / samples_per_frame;
+    first_sample = first_sample % samples_per_frame;
 
     /* parse 0xc header (header samples are not written to outbuf) */
-    coefs  = read_32bitLE(frame_offset + 0x00, stream->streamfile);
-    shifts = read_32bitLE(frame_offset + 0x04, stream->streamfile);
-    hist1  = read_16bitLE(frame_offset + 0x08, stream->streamfile);
-    hist2  = read_16bitLE(frame_offset + 0x0a, stream->streamfile);
+    frame_offset = stream->offset + bytes_per_frame * frames_in;
+    read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
+    coefs  = get_u32le(frame + 0x00);
+    shifts = get_u32le(frame + 0x04);
+    hist1  = get_s16le(frame + 0x08);
+    hist2  = get_s16le(frame + 0x0a);
 
 
     /* decode nibbles, grouped in 8 sets of 0x10 * 0x04 * 2 */
     for (i = 0; i < 8; i++) {
-        int32_t coef1, coef2, shift, coef_index, shift_factor;
-        off_t group_offset = frame_offset + 0x0c + 0x10*i;
+        int index, shift, coef1, coef2;
 
         /* each set has its own coefs/shifts (indexes > 7 are repeat, ex. 0x9 is 0x2) */
-        coef_index   = ((coefs >> i*4) & 0x0f) % 0x07;
-        shift_factor = (shifts >> i*4) & 0x0f;
+        index = ((coefs >> i*4) & 0x0f) % 0x07;
+        shift = (shifts >> i*4) & 0x0f;
 
-        coef1 = fadpcm_coefs[coef_index][0];
-        coef2 = fadpcm_coefs[coef_index][1];
-        shift = 0x16 - shift_factor; /* pre-adjust for 32b sign extend */
+        coef1 = fadpcm_coefs[index][0];
+        coef2 = fadpcm_coefs[index][1];
+        shift = 22 - shift; /* pre-adjust for 32b sign extend */
 
         for (j = 0; j < 4; j++) {
-            uint32_t nibbles = read_32bitLE(group_offset + 0x04*j, stream->streamfile);
+            uint32_t nibbles = get_u32le(frame + 0x0c + 0x10*i + 0x04*j);
 
             for (k = 0; k < 8; k++) {
-                int32_t new_sample;
+                int32_t sample;
 
-                new_sample = (nibbles >> k*4) & 0x0f;
-                new_sample = (new_sample << 28) >> shift; /* 32b sign extend + scale */
-                new_sample = (new_sample - hist2*coef2 + hist1*coef1);
-                new_sample = new_sample >> 6;
-                new_sample = clamp16(new_sample);
+                sample = (nibbles >> k*4) & 0x0f;
+                sample = (sample << 28) >> shift; /* 32b sign extend + scale */
+                sample = (sample - hist2*coef2 + hist1*coef1) >> 6;
+                sample = clamp16(sample);
 
                 if (sample_count >= first_sample && samples_done < samples_to_do) {
-                    outbuf[samples_done * channelspacing] = new_sample;
+                    outbuf[samples_done * channelspacing] = sample;
                     samples_done++;
                 }
                 sample_count++;
 
                 hist2 = hist1;
-                hist1 = new_sample;
+                hist1 = sample;
             }
         }
     }
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c
index 046d91a7c..c038e2b24 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c
@@ -557,16 +557,17 @@ fail:
  * float requirements, but C99 adds some faster-but-less-precise casting functions
  * we try to use (returning "long", though). They work ok without "fast float math" compiler
  * flags, but probably should be enabled anyway to ensure no extra IEEE checks are needed.
+ * MSVC added this in VS2015 (_MSC_VER 1900) but don't seem correctly optimized and is very slow.
  */
 static inline int float_to_int(float val) {
-#if defined(_MSC_VER) && (_MSC_VER < 1900) /* VS2015 */
+#if defined(_MSC_VER)
     return (int)val;
 #else
     return lrintf(val);
 #endif
 }
 static inline int double_to_int(double val) {
-#if defined(_MSC_VER) && (_MSC_VER < 1900) /* VS2015 */
+#if defined(_MSC_VER)
     return (int)val;
 #else
     return lrint(val); /* returns long tho */
@@ -580,9 +581,9 @@ static inline int double_to_int(double val) {
  * keep it simple for now until more tests are done.
  *
  * in normal (interleaved) formats samples are laid out straight
- *  (ibuf[s*chs+ch], ex. 4ch with 8s: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3)
+ *  (ibuf[s*chs+ch], ex. 4ch with 4s: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3)
  * in "p" (planar) formats samples are in planes per channel
- *  (ibuf[ch][s], ex. 4ch with 8s: 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3)
+ *  (ibuf[ch][s], ex. 4ch with 4s: 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3)
  *
  * alt float clamping:
  *  clamp_float(f32)
@@ -731,7 +732,6 @@ void decode_ffmpeg(VGMSTREAM *vgmstream, sample_t * outbuf, int32_t samples_to_d
 
                 copy_samples(data, outbuf, samples_to_get);
 
-                //samples_done += samples_to_get;
                 samples_to_do -= samples_to_get;
                 outbuf += samples_to_get * channels;
             }
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_utils.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_utils.c
index 3b6743104..d1a5feb8e 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_utils.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_utils.c
@@ -133,7 +133,8 @@ ffmpeg_codec_data * init_ffmpeg_atrac3_riff(STREAMFILE *sf, off_t offset, int* o
     /* implicit skip: official tools skip this even with encoder delay forced to 0. Maybe FFmpeg decodes late,
      * but when forcing tools to decode all frame samples it always ends a bit before last frame, so maybe it's
      * really an internal skip, since encoder adds extra frames so fact num_samples + encoder delay + implicit skip
-     * never goes past file. Same for all bitrate/channels, not added to loops. */
+     * never goes past file. Same for all bitrate/channels, not added to loops. This is probably "decoder delay"
+     * also seen in codecs like MP3 */
     if (is_at3) {
         implicit_skip = 69;
     }
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c
index c58327565..1c290e4c6 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c
@@ -878,7 +878,7 @@ void decode_fsb_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t *
     stream->adpcm_step_index = step_index;
 }
 
-/* mono XBOX-IMA with header endianness and alt nibble expand (per hcs's decompilation) */
+/* mono XBOX-IMA with header endianness and alt nibble expand (verified vs AK test demos) */
 void decode_wwise_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
     int i, sample_count = 0, num_frame;
     int32_t hist1 = stream->adpcm_history1_32;
@@ -922,17 +922,6 @@ void decode_wwise_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t
     stream->adpcm_history1_32 = hist1;
     stream->adpcm_step_index = step_index;
 }
-/* from hcs's analysis Wwise IMA expands nibbles slightly different, reducing dbs. Just "MUL" expand?
-<_ZN13CAkADPCMCodec12DecodeSampleEiii>: //From Wwise_v2015.1.6_Build5553_SDK.Linux
-  10:   83 e0 07                and    $0x7,%eax        ; sample
-  13:   01 c0                   add    %eax,%eax        ; sample*2
-  15:   83 c0 01                add    $0x1,%eax        ; sample*2+1
-  18:   0f af 45 e4             imul   -0x1c(%rbp),%eax ; (sample*2+1)*scale
-  1c:   8d 50 07                lea    0x7(%rax),%edx   ; result+7
-  1f:   85 c0                   test   %eax,%eax        ; result negative?
-  21:   0f 48 c2                cmovs  %edx,%eax        ; adjust if negative to fix rounding for below division
-  24:   c1 f8 03                sar    $0x3,%eax        ; (sample*2+1)*scale/8
-*/
 
 /* MS-IMA with possibly the XBOX-IMA model of even number of samples per block (more tests are needed) */
 void decode_awc_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/lsf_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/lsf_decoder.c
index 780aed132..66b5510a2 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/lsf_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/lsf_decoder.c
@@ -1,50 +1,60 @@
 #include "coding.h"
 #include "../util.h"
 
-/* lsf ADPCM, as seen in Fastlane Street Racing */
 
-static const short lsf_coefs[5][2] = {
-    {0x73, -0x34},
-    {0, 0},
-    {0x62, -0x37},
-    {0x3C, 0},
-    {0x7A, -0x3c}
+/* tweaked XA/PSX coefs << 6 */
+static const short lsf_coefs[16][2] = {
+    { 115, -52 },
+    {   0,   0 },
+    {  98, -55 },
+    {  60,   0 },
+    { 122, -60 },
+    /* rest assumed to be 0s */
 };
 
-void decode_lsf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
-    int i=first_sample;
-    int32_t sample_count;
-    const int bytes_per_frame = 0x1c;
-    const int samples_per_frame = (bytes_per_frame-1)*2;
-
-    int framesin = first_sample/samples_per_frame;
-
-    uint8_t q = 0xFF - read_8bit(framesin*bytes_per_frame + stream->offset,stream->streamfile);
-    int scale = (q&0xF0)>>4;
-    int coef_idx = q&0x0F;
+void decode_lsf(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
+    uint8_t frame[0x1c] = {0};
+    off_t frame_offset;
+    int i, frames_in, sample_count = 0;
+    int index, shift, coef1, coef2;
+    size_t bytes_per_frame, samples_per_frame;
     int32_t hist1 = stream->adpcm_history1_16;
     int32_t hist2 = stream->adpcm_history2_16;
+    uint8_t header;
 
-    first_sample = first_sample%samples_per_frame;
 
-    for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
-        int sample_byte = read_8bit(framesin*bytes_per_frame+stream->offset+1+i/2,stream->streamfile);
+    /* external interleave (fixed size), mono */
+    bytes_per_frame = 0x1c;
+    samples_per_frame = (bytes_per_frame - 1) * 2;
+    frames_in = first_sample / samples_per_frame;
+    //first_sample = first_sample % samples_per_frame; /* for flat layout */
 
-        int32_t prediction =
-            (hist1 * lsf_coefs[coef_idx][0] +
-             hist2 * lsf_coefs[coef_idx][1]) / 0x40;
+    /* external interleave (fixed size), mono */
+    frame_offset = stream->offset + bytes_per_frame * frames_in;
+    read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
+    header = 0xFF - frame[0x00];
+    shift = (header >> 4) & 0xf;
+    index = (header >> 0) & 0xf;
+    coef1 = lsf_coefs[index][0];
+    coef2 = lsf_coefs[index][1];
 
-        prediction += (i&1?
-            get_high_nibble_signed(sample_byte):
-            get_low_nibble_signed(sample_byte)
-        ) * (1 << (12-scale));
+    /* decode nibbles */
+    for (i = first_sample; i < first_sample + samples_to_do; i++) {
+        uint8_t nibbles = frame[0x01 + i/2];
+        int32_t sample;
 
-        prediction = clamp16(prediction);
+        sample = i&1 ? /* low nibble first */
+                get_high_nibble_signed(nibbles) :
+                get_low_nibble_signed(nibbles);
+        sample = sample * (1 << (12 - shift));
+        sample = sample + (hist1 * coef1 + hist2 * coef2) / 64; /* >> 6 */
+        sample = clamp16(sample);
+
+        outbuf[sample_count] = sample;
+        sample_count += channelspacing;
 
         hist2 = hist1;
-        hist1 = prediction;
-
-        outbuf[sample_count] = prediction;
+        hist1 = sample;
     }
 
     stream->adpcm_history1_16 = hist1;
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c
index 57ec9dce8..16d2b13b9 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c
@@ -92,6 +92,14 @@ int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, m
             data->skip_samples = data->config.skip_samples; break;
         case MPEG_STANDARD:
             data->skip_samples = data->config.skip_samples; break;
+        case MPEG_EA:
+            /* typical MP2 decoder delay, verified vs sx.exe, also SCHl blocks header takes discard
+             * samples into account (so block_samples+240*2+1 = total frame samples) */
+            if (info.layer == 2) {
+                data->skip_samples = 240*2 + 1;
+            }
+            /* MP3 probably uses 576 + 528+1 but no known games use it */
+            break;
         default:
             break;
     }
@@ -234,7 +242,7 @@ fail:
  * Gets info from a MPEG frame header at offset. Normally you would use mpg123_info but somehow
  * it's wrong at times (maybe because we use an ancient version) so here we do our thing.
  */
-int mpeg_get_frame_info(STREAMFILE *streamfile, off_t offset, mpeg_frame_info * info) {
+static int mpeg_get_frame_info_h(uint32_t header, mpeg_frame_info *info) {
     /* index tables */
     static const int versions[4] = { /* MPEG 2.5 */ 3, /* reserved */ -1,  /* MPEG 2 */ 2, /* MPEG 1 */ 1 };
     static const int layers[4] = { -1,3,2,1 };
@@ -257,14 +265,11 @@ int mpeg_get_frame_info(STREAMFILE *streamfile, off_t offset, mpeg_frame_info *
             { 384, 1152, 576  }  /* MPEG2.5 */
     };
 
-    uint32_t header;
     int idx, padding;
 
 
     memset(info, 0, sizeof(*info));
 
-    header = read_32bitBE(offset, streamfile);
-
     if ((header >> 21) != 0x7FF) /* 31-21: sync */
         goto fail;
 
@@ -308,34 +313,36 @@ int mpeg_get_frame_info(STREAMFILE *streamfile, off_t offset, mpeg_frame_info *
 fail:
     return 0;
 }
+int mpeg_get_frame_info(STREAMFILE *sf, off_t offset, mpeg_frame_info *info) {
+    uint32_t header = read_u32be(offset, sf);
+    return mpeg_get_frame_info_h(header, info);
+}
 
-size_t mpeg_get_samples(STREAMFILE *streamFile, off_t start_offset, size_t bytes) {
+size_t mpeg_get_samples(STREAMFILE *sf, off_t start_offset, size_t bytes) {
     off_t offset = start_offset;
     off_t max_offset = start_offset + bytes;
-    int samples = 0;
+    int frames = 0, samples = 0, encoder_delay = 0, encoder_padding = 0;
     mpeg_frame_info info;
-    size_t prev_size = 0;
-    int cbr_count = 0;
-    int is_vbr = 0;
 
-    if (!streamFile)
+    if (!sf)
         return 0;
 
-    if (max_offset > get_streamfile_size(streamFile))
-        max_offset = get_streamfile_size(streamFile);
+    if (max_offset > get_streamfile_size(sf))
+        max_offset = get_streamfile_size(sf);
 
     /* MPEG may use VBR so must read all frames */
     while (offset < max_offset) {
+        uint32_t header = read_u32be(offset+0x00, sf);
 
         /* skip ID3v2 */
-        if ((read_32bitBE(offset+0x00, streamFile) & 0xFFFFFF00) == 0x49443300) { /* "ID3\0" */
+        if ((header & 0xFFFFFF00) == 0x49443300) { /* "ID3\0" */
             size_t frame_size = 0;
-            uint8_t flags = read_8bit(offset+0x05, streamFile);
+            uint8_t flags = read_u8(offset+0x05, sf);
             /* this is how it's officially read :/ */
-            frame_size += read_8bit(offset+0x06, streamFile) << 21;
-            frame_size += read_8bit(offset+0x07, streamFile) << 14;
-            frame_size += read_8bit(offset+0x08, streamFile) << 7;
-            frame_size += read_8bit(offset+0x09, streamFile) << 0;
+            frame_size += read_u8(offset+0x06, sf) << 21;
+            frame_size += read_u8(offset+0x07, sf) << 14;
+            frame_size += read_u8(offset+0x08, sf) << 7;
+            frame_size += read_u8(offset+0x09, sf) << 0;
             frame_size += 0x0a;
             if (flags & 0x10) /* footer? */
                 frame_size += 0x0a;
@@ -344,28 +351,71 @@ size_t mpeg_get_samples(STREAMFILE *streamFile, off_t start_offset, size_t bytes
             continue;
         }
 
-        /* this may fail with unknown ID3 tags */
-        if (!mpeg_get_frame_info(streamFile, offset, &info))
-            break;
-
-        if (prev_size && prev_size != info.frame_size) {
-            is_vbr = 1;
-        }
-        else if (!is_vbr) {
-            cbr_count++;
+        /* skip ID3v1 */
+        if ((header & 0xFFFFFF00) == 0x54414700) { /* "TAG\0" */
+            ;VGM_LOG("MPEG: ID3v1 at %lx\n", offset);
+            offset += 0x80;
+            continue;
         }
 
-        if (cbr_count >= 10) {
-            /* must be CBR, don't bother counting */
-            samples = (bytes / info.frame_size) * info.frame_samples;
+        /* regular frame */
+        if (!mpeg_get_frame_info_h(header, &info)) {
+            VGM_LOG("MPEG: unknown frame at %lx\n", offset);
             break;
         }
 
+        /* detect Xing header (disguised as a normal frame) */
+        if (frames < 3 && /* should be first after tags */
+                info.frame_size >= 0x24 + 0x78 &&
+                read_u32be(offset + 0x04, sf) == 0 &&
+                (read_u32be(offset + 0x24, sf) == 0x58696E67 ||  /* "Xing" (mainly for VBR) */
+                 read_u32be(offset + 0x24, sf) == 0x496E666F)) { /* "Info" (mainly for CBR) */
+            uint32_t flags = read_u32be(offset + 0x28, sf);
+
+            if (flags & 1) { /* other flags indicate seek table and stuff */
+                uint32_t frame_count = read_u32be(offset + 0x2c, sf);
+                samples = frame_count * info.frame_samples;
+            }
+
+            /* vendor specific */
+            if (info.frame_size > 0x24 + 0x78 + 0x24 &&
+                    read_u32be(offset + 0x9c, sf) == 0x4C414D45) { /* "LAME" */
+                if (info.layer == 3) {
+                    uint32_t delays = read_u32be(offset + 0xb0, sf);
+                    encoder_delay   = ((delays >> 12) & 0xFFF);
+                    encoder_padding =  ((delays >> 0) & 0xFFF);
+
+                    encoder_delay += (528 + 1); /* implicit MDCT decoder delay (seen in LAME source) */
+                    if (encoder_padding > 528 + 1)
+                        encoder_padding -= (528 + 1);
+                }
+                else {
+                    encoder_delay = 240 + 1;
+                }
+
+                /* replay gain and stuff */
+            }
+
+            /* there is also "iTunes" vendor with no apparent extra info, iTunes delays are in "iTunSMPB" ID3 tag */
+
+            ;VGM_LOG("MPEG: found Xing header\n");
+            break; /* we got samples */
+        }
+
+        //TODO: detect "VBRI" header (Fraunhofer encoder)
+        // https://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header#VBRIHeader
+
+        /* could detect VBR/CBR but read frames to remove ID3 end tags */
+
+        frames++;
         offset += info.frame_size;
-        prev_size = info.frame_size;
-        samples += info.frame_samples; /* header frames may be 0? */
+        samples += info.frame_samples;
     }
 
+    ;VGM_LOG("MPEG: samples=%i, ed=%i, ep=%i, end=%i\n", samples,encoder_delay,encoder_padding, samples - encoder_delay - encoder_padding);
+
+    //todo return encoder delay
+    samples = samples - encoder_delay - encoder_padding;
     return samples;
 }
 
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c
index 4577894b8..536db8308 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c
@@ -112,12 +112,16 @@ int mpeg_custom_setup_init_ealayer3(STREAMFILE *streamfile, off_t start_offset,
         ok = ealayer3_parse_frame(data, -1, &ib, &eaf);
         if (!ok) goto fail;
     }
-    //;VGM_ASSERT(!eaf.mpeg1, "EAL3: mpeg2 found at 0x%lx\n", start_offset); /* rare [FIFA 08 (PS3) abk] */
+    ;VGM_ASSERT(!eaf.mpeg1, "EAL3: mpeg2 found at 0x%lx\n", start_offset); /* rare [FIFA 08 (PS3) abk] */
 
     *coding_type = coding_MPEG_ealayer3;
     data->channels_per_frame = eaf.channels;
     data->samples_per_frame = eaf.mpeg1 ? 1152 : 576;
 
+    /* handled at frame start */
+    //data->skip_samples = 576 + 529;
+    //data->samples_to_discard = data->skip_samples;
+
     /* encoder delay: EALayer3 handles this while decoding (skips samples as writes PCM blocks) */
 
     return 1;
@@ -133,6 +137,16 @@ int mpeg_custom_parse_frame_ealayer3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *
     ealayer3_frame_t eaf_0, eaf_1;
 
 
+    /* the first frame samples must be discarded (verified vs sx.exe with a file without PCM blocks),
+     * but we can't set samples_to_discard since PCM blocks would be discarded
+     * SCHl block samples field takes into account this discard (its value already substracts this) */
+    if ((data->type == MPEG_EAL31 || data->type == MPEG_EAL31b) && ms->current_size_count == 0) {
+        /* seems true for MP2/576 frame samples too, though they are rare so it's hard to test */
+        ms->decode_to_discard += 529 + 576; /* standard MP3 decoder delay + 1 granule samples */
+        ms->current_size_count++;
+    }
+
+
     /* read first frame/granule, or PCM-only frame (found alone at the end of SCHl streams) */
 	{
         //;VGM_LOG("s%i: get granule0 at %lx\n", num_stream,stream->offset);
@@ -317,7 +331,7 @@ static int ealayer3_parse_frame_v1(ealayer3_buffer_t *ib, ealayer3_frame_t *eaf,
     /* check PCM block */
     if (eaf->v1_pcm_flag == 0xEE) {
         fill_buf(ib, 32);
-        r_bits(is, 16,&eaf->v1_offset_samples); /* samples to discard of the next decoded (not PCM block) samples */
+        r_bits(is, 16,&eaf->v1_offset_samples); /* PCM block offset in the buffer */
         r_bits(is, 16,&eaf->v1_pcm_samples); /* number of PCM samples, can be 0 */
 
         eaf->pre_size += 2+2; /* 16b+16b */
@@ -672,8 +686,7 @@ static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_n
 }
 
 /* write PCM block directly to sample buffer and setup decode discard (EALayer3 seems to use this as a prefetch of sorts).
- * Meant to be written inmediatedly, as those PCM are parts that can be found after 1 decoded frame.
- * (ex. EA-frame_gr0, PCM-frame_0, EA-frame_gr1, PCM-frame_1 actually writes PCM-frame_0+1, decode of EA-frame_gr0+1 + discard part */
+ * Seems to alter decoded sample buffer to handle encoder delay/padding in a twisted way. */
 static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, ealayer3_frame_t *eaf) {
     mpeg_custom_stream *ms = data->streams[num_stream];
     int channels_per_frame = ms->channels_per_frame;
@@ -694,38 +707,34 @@ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *d
     if (eaf->v1_pcm_samples || eaf->v1_offset_samples) {
         uint8_t* outbuf = ms->output_buffer + bytes_filled;
         off_t pcm_offset = stream->offset + eaf->pre_size + eaf->common_size;
+        size_t decode_to_discard;
 
         VGM_ASSERT(eaf->v1_offset_samples > 576, "EAL3: big discard %i at 0x%x\n", eaf->v1_offset_samples, (uint32_t)stream->offset);
         VGM_ASSERT(eaf->v1_pcm_samples > 0x100, "EAL3: big samples %i at 0x%x\n", eaf->v1_pcm_samples, (uint32_t)stream->offset);
         VGM_ASSERT(eaf->v1_offset_samples > 0 && eaf->v1_pcm_samples == 0, "EAL3: offset_samples without pcm_samples\n"); /* not seen but could work */
 
-        //;VGM_LOG("EA EAL3 v1: off=%lx, discard=%x, pcm=%i, pcm_o=%lx\n",
-        //        stream->offset, eaf->v1_offset_samples, eaf->v1_pcm_samples, pcm_offset);
+        //;VGM_LOG("EA EAL3 v1: offset=%lx + %x, offset_samples=%x, pcm_samples=%i, spf=%i\n",
+        //        stream->offset, eaf->pre_size + eaf->common_size, eaf->v1_offset_samples, eaf->v1_pcm_samples, data->samples_per_frame);
 
-        /* V1 usually discards + copies samples at the same time
-         * V1b PCM block is in 'planar' format (ex. NFS:U PS3) */
+        /* V1b PCM block is in 'planar' format (ex. NFS:U PS3) */
         ealayer3_copy_pcm_block(outbuf, pcm_offset, eaf->v1_pcm_samples, channels_per_frame, (data->type == MPEG_EAL31), stream->streamfile);
         ms->samples_filled += eaf->v1_pcm_samples;
 
-        /* skip decoded samples as PCM block 'overwrites' them w/ special meanings */
-        {
-            size_t decode_to_discard = eaf->v1_offset_samples;
+        //TODO: we should put samples at offset but most EAL3 use it at first frame, which decodes ok, and rarely
+        //  in the last frame [ex. Celebrity Sports Showdown], which is ~60-80 samples off (could click on segments?)
 
-            if (data->type == MPEG_EAL31) {
-                //todo should also discard v1_pcm_samples, but block layout samples may be exhausted
-                // and won't move (maybe new block if offset = new offset detected)
-                if (decode_to_discard == 576)
-                    decode_to_discard = data->samples_per_frame;//+ eaf->v1_pcm_samples;
-            }
-            else {
-                VGM_ASSERT(decode_to_discard > 0, "EAL3: found offset_samples in V1b\n");
-                /* probably (576 or samples_per_frame - eaf->v1_offset_samples) but V1b seems to always use 0 and ~47 samples */
-                if (decode_to_discard == 0) /* seems ok (ex. comparing NFS:UC PS3 vs PC gets correct waveform this way) */
-                    decode_to_discard = data->samples_per_frame;//+ eaf->v1_pcm_samples; /* musn't discard pcm_number */
-            }
-
-            ms->decode_to_discard += decode_to_discard;
-        }
+        /* v1_offset_samples in V1a controls how the PCM block goes in the sample buffer. Value seems to start
+         * from frame samples end, taking into account that 1st frame discards 576+529 samples.
+         * ex. with 47 samples:
+         * - offset 47 puts block at sample 0 (at 576*2-47 w/o 576+529 discard),
+         * - offset 63 puts block at sample -16 (only 31 samples visible, so 576*2-63 w/o discard),
+         * - offset 0 seems to cause sample buffer overrun (at  576*2-0 = outside single frame buffer?)
+         * In V1b seems to works similarly but offset looks adjusted after initial discard (so offset 0 for first frame)
+         *
+         * This behaviour matters most in looping sfx (ex. Burnout Paradise), or tracks that start
+         * without silence (ex. NFS:UG2), and NFS:UC PS3 (EAL3v1b) vs PC (EAXAS) gets correct waveform this way */
+        decode_to_discard = eaf->v1_pcm_samples;
+        ms->decode_to_discard += decode_to_discard;
     }
 
     if (eaf->v2_extended_flag) {
@@ -789,6 +798,11 @@ fail:
 }
 
 
+//TODO: this causes lots of rebuffering/slowness in multichannel since each stream has to read back
+// (frames are interleaved like s0_g0, s1_g0, s2_g0, s0_g1, s1_g1, s2_g1, ...,
+//  stream0 advances buffers to s0_g1, but stream1 needs to read back to s1_g0, often trashing custom IO)
+// would need to store granule0 after reading but not decoding until next?
+
 /* Skip EA-frames from other streams for .sns/sps multichannel (interleaved 1 EA-frame per stream).
  * Due to EALayer3 being in blocks and other complexities (we can't go past a block) all
  * streams's offsets should start in the first stream's EA-frame.
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c
index 32da9dd14..0a19a050e 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c
@@ -204,7 +204,7 @@ static mpg123_handle * init_mpg123_handle() {
     }
 
     mpg123_param(m,MPG123_REMOVE_FLAGS,MPG123_GAPLESS,0.0); /* wonky support */
-    mpg123_param(m,MPG123_RESYNC_LIMIT, -1, 0x10000); /* should be enough */
+    mpg123_param(m,MPG123_RESYNC_LIMIT, -1, 0x2000); /* just in case, games shouldn't ever need this */
 
     if (mpg123_open_feed(m) != MPG123_OK) {
         goto fail;
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mta2_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/mta2_decoder.c
index 06480c6da..a6df7870f 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/mta2_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/mta2_decoder.c
@@ -4,6 +4,7 @@
 /* MTA2 decoder based on:
  * - MGS Developer Wiki: https://www.mgsdevwiki.com/wiki/index.php/MTA2_(Codec) [codec by daemon1]
  * - Solid4 tools: https://github.com/GHzGangster/Drebin
+ * (PS3 probably uses floats, so this may not be 100% accurate)
  *
  * MTA2 layout:
  * - data is divided into N tracks of 0x10 header + 0x90 frame per track channel, forming N streams
@@ -17,34 +18,28 @@
  * expects samples_to_do to be block_samples at most (could be simplified, I guess).
  */
 
-/* coefs table (extended XA filters) */
-static const int mta2_coefs1[8] = {
-    0,  240,  460,  392,  488,  460,  460,  240 
+/* tweaked XA/PSX coefs << 8 */
+static const int16_t mta2_coefs[8][2] = {
+    {   0,    0 },
+    { 240,    0 },
+    { 460, -208 },
+    { 392, -220 },
+    { 488, -240 },
+    { 460, -240 },
+    { 460, -220 },
+    { 240, -104 }
 };
-static const int mta2_coefs2[8] = {
-    0,    0, -208, -220, -240, -240, -220, -104 
-};
-/* shift table */
-static const int mta2_shifts[32] = {
+
+static const int mta2_scales[32] = {
        256,    335,    438,    573,    749,    979,   1281,    1675, 
       2190,   2864,   3746,   4898,   6406,   8377,  10955,   14327, 
      18736,  24503,  32043,  41905,  54802,  71668,  93724,  122568,
     160290, 209620, 274133, 358500, 468831, 613119, 801811, 1048576
 };
 
-/* expands nibble */
-static short mta2_expand_nibble(int nibble, short hist1, short hist2, int coef_index, int shift_index) {
-    int output;
-    if (nibble > 7) /* sign extend */
-        nibble = nibble - 16;
-
-    output = (hist1 * mta2_coefs1[coef_index] + hist2 * mta2_coefs2[coef_index] + (nibble * mta2_shifts[shift_index]) + 128) >> 8;
-    output = clamp16(output);
-    return (short)output;
-}
-
 /* decodes a block for a channel */
-void decode_mta2(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
+void decode_mta2(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
+    uint8_t frame[0x10 + 0x90*8] = {0};
     int samples_done = 0, sample_count = 0, channel_block_samples, channel_first_sample, frame_size = 0;
     int i, group, row, col;
     int track_channels = 0, track_channel;
@@ -55,14 +50,14 @@ void decode_mta2(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
         int num_track = 0, channel_layout;
 
         /* parse track header (0x10) and skip tracks that our current channel doesn't belong to */
-        num_track      =    read_8bit(stream->offset+0x00,stream->streamfile); /* 0=first */
+        read_streamfile(frame, stream->offset, 0x10, stream->streamfile); /* ignore EOF errors */
+        num_track      = get_u8   (frame + 0x00); /* 0=first */
         /* 0x01(3): num_frame (0=first) */
         /* 0x04(1): 0? */
-        channel_layout =    read_8bit(stream->offset+0x05,stream->streamfile); /* bitmask, see mta2.c */
-        frame_size     = read_16bitBE(stream->offset+0x06,stream->streamfile); /* not including this header */
+        channel_layout = get_u8   (frame + 0x05); /* bitmask, see mta2.c */
+        frame_size     = get_u16be(frame + 0x06); /* not including this header */
         /* 0x08(8): null */
 
-
         VGM_ASSERT(frame_size == 0, "MTA2: empty frame at %x\n", (uint32_t)stream->offset);
         /* frame_size 0 means silent/empty frame (rarely found near EOF for one track but not others)
          * negative track only happens for truncated files (EOF) */
@@ -74,13 +69,13 @@ void decode_mta2(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
         }
 
         track_channels = 0;
-        for (i = 0; i < 8; i++) {
+        for (i = 0; i < 8; i++) { /* max 8ch */
             if ((channel_layout >> i) & 0x01)
                 track_channels++;
         }
 
         if (track_channels == 0) { /* bad data, avoid div by 0 */
-            VGM_LOG("track_channels 0 at %x\n", (uint32_t)stream->offset);
+            VGM_LOG("MTA2: track_channels 0 at %x\n", (uint32_t)stream->offset);
             return;
         }
 
@@ -93,19 +88,20 @@ void decode_mta2(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
     }
     while (1);
 
+    /* parse stuff */
+    read_streamfile(frame + 0x10, stream->offset + 0x10, frame_size, stream->streamfile); /* ignore EOF errors */
     track_channel = channel % track_channels;
     channel_block_samples = (0x80*2);
     channel_first_sample = first_sample % (0x80*2);
 
-
     /* parse channel frame (header 0x04*4 + data 0x20*4) */
     for (group = 0; group < 4; group++) {
-        short hist2, hist1, coefs, shift, output;
-        int group_header = read_32bitBE(stream->offset + 0x10 + track_channel*0x90 + group*0x4, stream->streamfile);
+        short hist2, hist1, coefs, scale;
+        uint32_t group_header = get_u32be(frame + 0x10 + track_channel*0x90 + group*0x4);
         hist2 = (short) ((group_header >> 16) & 0xfff0); /* upper 16b discarding 4b */
         hist1 = (short) ((group_header >>  4) & 0xfff0); /* lower 16b discarding 4b */
         coefs = (group_header >> 5) & 0x7; /* mid 3b */
-        shift = group_header & 0x1f; /* lower 5b */
+        scale = group_header & 0x1f; /* lower 5b */
 
         /* write header samples (skips the last 2 group nibbles), like Drebin's decoder
          * last 2 nibbles and next 2 header hist should match though */
@@ -120,23 +116,31 @@ void decode_mta2(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
         }
         sample_count++;
 
+        /* decode nibbles */
         for (row = 0; row < 8; row++) {
+            int pos = 0x10 + track_channel*0x90 + 0x10 + group*0x4 + row*0x10;
             for (col = 0; col < 4*2; col++) {
-                uint8_t nibbles = read_8bit(stream->offset + 0x10 + 0x10 + track_channel*0x90 + group*0x4 + row*0x10 + col/2, stream->streamfile);
-                int nibble_shift = (!(col&1) ? 4 : 0); /* upper first */
-                output = mta2_expand_nibble((nibbles >> nibble_shift) & 0xf, hist1, hist2, coefs, shift);
+                uint8_t nibbles = frame[pos + col/2];
+                int32_t sample;
+
+                sample = col&1 ? /* high nibble first */
+                        get_low_nibble_signed(nibbles) :
+                        get_high_nibble_signed(nibbles);
+                sample = sample * mta2_scales[scale];
+                sample = (sample + hist1 * mta2_coefs[coefs][0] + hist2 * mta2_coefs[coefs][1] + 128) >> 8;
+                sample = clamp16(sample);
 
                 /* ignore last 2 nibbles (uses first 2 header samples) */
                 if (row < 7 || col < 3*2) {
                     if (sample_count >= channel_first_sample && samples_done < samples_to_do) {
-                        outbuf[samples_done * channelspacing] = output;
+                        outbuf[samples_done * channelspacing] = sample;
                         samples_done++;
                     }
                     sample_count++;
                 }
 
                 hist2 = hist1;
-                hist1 = output;
+                hist1 = sample;
             }
         }
     }
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mtaf_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/mtaf_decoder.c
index bc07dfcf0..38a5a38a1 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/mtaf_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/mtaf_decoder.c
@@ -8,12 +8,12 @@
  * Layout: N tracks of 0x10 header + 0x80*2 (always 2ch; multichannels uses 4ch = 2ch track0 + 2ch track1 xN).
  */
 
-static const int index_table[16] = {
+static const int mtaf_step_indexes[16] = {
     -1, -1, -1, -1, 2, 4, 6, 8,
     -1, -1, -1, -1, 2, 4, 6, 8
 }; 
 
-static const int16_t step_size[32][16] = {
+static const int16_t mtaf_step_sizes[32][16] = {
     {     1,     5,     9,    13,    16,    20,    24,    28,
          -1,    -5,    -9,   -13,   -16,   -20,   -24,   -28, },
     {     2,     6,    11,    15,    20,    24,    29,    33,
@@ -81,52 +81,56 @@ static const int16_t step_size[32][16] = {
 };
 
 
-void decode_mtaf(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
-    int32_t sample_count;
-    int i;
-    int c = channel%2; /* global channel to track channel */
+void decode_mtaf(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
+    uint8_t frame[0x110] = {0};
+    off_t frame_offset;
+    int i, ch, sample_count = 0;
+    size_t bytes_per_frame /*, samples_per_frame*/;
     int32_t hist = stream->adpcm_history1_16;
-    int32_t step_idx = stream->adpcm_step_index;
+    int32_t step_index = stream->adpcm_step_index;
 
 
-    /* read header when we hit a new track every 0x100 samples */
-    first_sample = first_sample % 0x100;
+    /* special stereo interleave, stereo */
+    bytes_per_frame = 0x10 + 0x80*2;
+    //samples_per_frame = (bytes_per_frame - 0x10) / 2 * 2; /* 256 */
+    ch = channel % 2; /* global channel to track channel */
+    //first_sample = first_sample % samples_per_frame; /* for flat layout */
 
+    /* read frame */
+    frame_offset = stream->offset;
+    read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
+
+    /* parse frame header when we hit a new track every frame samples */
     if (first_sample == 0) {
         /*  0x10 header: track (8b, 0=first), track count (24b, 1=first), step-L, step-R, hist-L, hist-R */
-        int32_t init_idx  = read_16bitLE(stream->offset+4+0+c*2, stream->streamfile); /* step-L/R */
-        int32_t init_hist = read_16bitLE(stream->offset+4+4+c*4, stream->streamfile); /* hist-L/R: hist 16bit + empty 16bit */
+        step_index = get_s16le(frame + 0x04 + 0x00 + ch*0x02); /* step-L/R */
+        hist       = get_s16le(frame + 0x04 + 0x04 + ch*0x04); /* hist-L/R: hist 16bit + empty 16bit */
 
-        VGM_ASSERT(init_idx < 0 || init_idx > 31, "MTAF: bad header idx @ 0x%x\n", (uint32_t)stream->offset);
-        /* avoid index out of range in corrupt files */
-        if (init_idx < 0) {
-            init_idx = 0;
-        } else if (init_idx > 31) {
-            init_idx = 31;
+        VGM_ASSERT(step_index < 0 || step_index > 31, "MTAF: bad header idx at 0x%x\n", (uint32_t)stream->offset);
+        if (step_index < 0) {
+            step_index = 0;
+        } else if (step_index > 31) {
+            step_index = 31;
         }
-
-        step_idx = init_idx;
-        hist = init_hist;
     }
 
+    /* decode nibbles */
+    for (i = first_sample; i < first_sample + samples_to_do; i++) {
+        uint8_t nibbles = frame[0x10 + 0x80*ch + i/2];
+        uint8_t nibble = (nibbles >> (!(i&1)?0:4)) & 0xf; /* lower first */
 
-    /* skip to nibble */
-    for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
-        uint8_t byte = read_8bit(stream->offset + 0x10 + 0x80*c + i/2, stream->streamfile);
-        uint8_t nibble = (byte >> (!(i&1)?0:4)) & 0xf; /* lower first */
-
-        hist = clamp16(hist+step_size[step_idx][nibble]);
+        hist = clamp16(hist + mtaf_step_sizes[step_index][nibble]);
         outbuf[sample_count] = hist;
+        sample_count += channelspacing;
 
-        step_idx += index_table[nibble];
-        if (step_idx < 0) { /* clip step */
-            step_idx = 0;
-        } else if (step_idx > 31) {
-            step_idx = 31;
+        step_index += mtaf_step_indexes[nibble];
+        if (step_index < 0) {
+            step_index = 0;
+        } else if (step_index > 31) {
+            step_index = 31;
         }
     }
 
-    /* update state */
-    stream->adpcm_step_index = step_idx;
+    stream->adpcm_step_index = step_index;
     stream->adpcm_history1_16 = hist;
 }
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/nds_procyon_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/nds_procyon_decoder.c
index 75f86125b..32466a670 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/nds_procyon_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/nds_procyon_decoder.c
@@ -1,58 +1,62 @@
 #include "coding.h"
 #include "../util.h"
 
-/* ADPCM found in NDS games using Procyon Studio Digital Sound Elements */
-
-static const int8_t proc_coef[5][2] =
-{
-    {0x00,0x00},
-    {0x3C,0x00},
-    {0x73,0xCC},
-    {0x62,0xC9},
-    {0x7A,0xC4},
+/* standard XA/PSX coefs << 6 */
+static const int8_t proc_coefs[16][2] = {
+    {   0,   0 },
+    {  60,   0 },
+    { 115, -52 },
+    {  98, -55 },
+    { 122, -60 },
+    /* rest is 0s */
 };
 
-void decode_nds_procyon(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
-    int i=first_sample;
-    int32_t sample_count;
-
-    int framesin = first_sample/30;
-
-    uint8_t header = read_8bit(framesin*16+15+stream->offset,stream->streamfile) ^ 0x80;
-    int scale = 12 - (header & 0xf);
-    int coef_index = (header >> 4) & 0xf;
+/* ADPCM found in NDS games using Procyon Studio Digital Sound Elements */
+void decode_nds_procyon(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
+    uint8_t frame[0x10] = {0};
+    off_t frame_offset;
+    int i, frames_in, sample_count = 0;
+    size_t bytes_per_frame, samples_per_frame;
+    int index, scale, coef1, coef2;
     int32_t hist1 = stream->adpcm_history1_32;
     int32_t hist2 = stream->adpcm_history2_32;
-    int32_t coef1;
-    int32_t coef2;
+    uint8_t header;
 
-    if (coef_index > 4) coef_index = 0;
-    coef1 = proc_coef[coef_index][0];
-    coef2 = proc_coef[coef_index][1];
-    first_sample = first_sample%30;
 
-    for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
-        int sample_byte = read_8bit(framesin*16+stream->offset+i/2,stream->streamfile) ^ 0x80;
+    /* external interleave (fixed size), mono */
+    bytes_per_frame = 0x10;
+    samples_per_frame = (bytes_per_frame - 0x01) * 2; /* 30 */
+    frames_in = first_sample / samples_per_frame;
 
-        int32_t sample = 
-                 (int32_t)
-                 (i&1?
-                    get_high_nibble_signed(sample_byte):
-                    get_low_nibble_signed(sample_byte)
-                   ) * 64 * 64;
+    /* parse frame header */
+    frame_offset = stream->offset + bytes_per_frame * frames_in;
+    read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
+    header = frame[0x0F] ^ 0x80;
+    scale = 12 - (header & 0xf);
+    index = (header >> 4) & 0xf;
+    coef1 = proc_coefs[index][0];
+    coef2 = proc_coefs[index][1];
+
+    /* decode nibbles */
+    for (i = first_sample; i < first_sample + samples_to_do; i++) {
+        uint8_t nibbles = frame[i/2] ^ 0x80;
+        int32_t sample = 0;
+
+        sample = i&1 ? /* low nibble first */
+                get_high_nibble_signed(nibbles) :
+                get_low_nibble_signed(nibbles);
+        sample = sample * 64 * 64; /* << 12 */
         if (scale < 0)
-        {
             sample <<= -scale;
-        }
         else
             sample >>= scale;
-
         sample = (hist1 * coef1 + hist2 * coef2 + 32) / 64  + (sample * 64);
 
         hist2 = hist1;
-        hist1 = sample;
+        hist1 = sample; /* clamp *after* this */
 
-        outbuf[sample_count] = clamp16((sample + 32) / 64) /  64 * 64;
+        outbuf[sample_count] = clamp16((sample + 32) / 64) / 64 * 64;
+        sample_count += channelspacing;
     }
 
     stream->adpcm_history1_32 = hist1;
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ngc_afc_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ngc_afc_decoder.c
index 8c796f951..8af87ff38 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/ngc_afc_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/ngc_afc_decoder.c
@@ -1,56 +1,66 @@
 #include "coding.h"
 #include "../util.h"
 
-const short afc_coef[16][2] =
-{{0,0},
-{0x0800,0},
-{0,0x0800},
-{0x0400,0x0400},
-{0x1000,0xf800},
-{0x0e00,0xfa00},
-{0x0c00,0xfc00},
-{0x1200,0xf600},
-{0x1068,0xf738},
-{0x12c0,0xf704},
-{0x1400,0xf400},
-{0x0800,0xf800},
-{0x0400,0xfc00},
-{0xfc00,0x0400},
-{0xfc00,0},
-{0xf800,0}};
+static const int16_t afc_coefs[16][2] = {
+        {    0,    0 },
+        { 2048,    0 },
+        {    0, 2048 },
+        { 1024, 1024 },
+        { 4096,-2048 },
+        { 3584,-1536 },
+        { 3072,-1024 },
+        { 4608,-2560 },
+        { 4200,-2248 },
+        { 4800,-2300 },
+        { 5120,-3072 },
+        { 2048,-2048 },
+        { 1024,-1024 },
+        {-1024, 1024 },
+        {-1024,    0 },
+        {-2048,    0 }
+};
 
-void decode_ngc_afc(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
-    int i=first_sample;
-    int32_t sample_count;
-
-    int framesin = first_sample/16;
-
-    int8_t header = read_8bit(framesin*9+stream->offset,stream->streamfile);
-    int32_t scale = 1 << ((header>>4) & 0xf);
-    int coef_index = (header & 0xf);
+void decode_ngc_afc(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
+    uint8_t frame[0x09] = {0};
+    off_t frame_offset;
+    int i, frames_in, sample_count = 0;
+    size_t bytes_per_frame, samples_per_frame;
+    int index, scale, coef1, coef2;
     int32_t hist1 = stream->adpcm_history1_16;
     int32_t hist2 = stream->adpcm_history2_16;
-    int coef1 = afc_coef[coef_index][0];
-    int coef2 = afc_coef[coef_index][1];
-    /*printf("offset: %x\nscale: %d\nindex: %d (%lf,%lf)\nhist: %d %d\n",
-            (unsigned)stream->offset,scale,coef_index,coef1/2048.0,coef2/2048.0,hist1,hist2);*/
 
-    first_sample = first_sample%16;
 
-    for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
-        int sample_byte = read_8bit(framesin*9+stream->offset+1+i/2,stream->streamfile);
+    /* external interleave, mono */
+    bytes_per_frame = 0x09;
+    samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 16 */
+    frames_in = first_sample / samples_per_frame;
 
-        outbuf[sample_count] = clamp16((
-                 (((i&1?
-                    get_low_nibble_signed(sample_byte):
-                    get_high_nibble_signed(sample_byte)
-                   ) * scale)<<11) +
-                 (coef1 * hist1 + coef2 * hist2))>>11
-                );
-        /*printf("%hd\n",outbuf[sample_count]);*/
+    /* parse frame header */
+    frame_offset = stream->offset + bytes_per_frame * frames_in;
+    read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
+    scale = 1 << ((frame[0] >> 4) & 0xf);
+    index = (frame[0] & 0xf);
+    coef1 = afc_coefs[index][0];
+    coef2 = afc_coefs[index][1];
+
+    /* decode nibbles */
+    for (i = first_sample; i < first_sample + samples_to_do; i++) {
+        uint8_t nibbles = frame[0x01 + i/2];
+        int32_t sample;
+
+        sample = i&1 ? /* high nibble first */
+                get_low_nibble_signed(nibbles) :
+                get_high_nibble_signed(nibbles);
+        sample = ((sample * scale) << 11);
+        sample = (sample + coef1*hist1 + coef2*hist2) >> 11;
+
+        sample = clamp16(sample);
+
+        outbuf[sample_count] = sample;
+        sample_count += channelspacing;
 
         hist2 = hist1;
-        hist1 = outbuf[sample_count];
+        hist1 = sample;
     }
 
     stream->adpcm_history1_16 = hist1;
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ngc_dtk_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ngc_dtk_decoder.c
index 4ea57cc07..262a55ab5 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/ngc_dtk_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/ngc_dtk_decoder.c
@@ -2,63 +2,61 @@
 #include "../util.h"
 
 
-/* Nintendo GC Disc TracK streaming ADPCM (similar to CD-XA) */
-void decode_ngc_dtk(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
+/* standard XA coefs << 6 */
+static const int8_t dtk_coefs[16][2] = {
+        {   0,  0 },
+        {  60,  0 },
+        { 115, 52 },
+        {  98, 55 },
+        /* rest assumed to be 0s */
+};
+
+/* Nintendo GC Disc TracK streaming ADPCM (similar to XA) */
+void decode_ngc_dtk(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
+    uint8_t frame[0x20] = {0};
     off_t frame_offset;
     int i, frames_in, sample_count = 0;
     size_t bytes_per_frame, samples_per_frame;
-    uint8_t coef_index, shift_factor;
+    int index, shift, coef1, coef2;
     int32_t hist1 = stream->adpcm_history1_32;
     int32_t hist2 = stream->adpcm_history2_32;
 
 
     /* external interleave (fixed size), stereo */
     bytes_per_frame = 0x20;
-    samples_per_frame = 28;
+    samples_per_frame = (0x20 - 0x04); /* 28 for each channel */
     frames_in = first_sample / samples_per_frame;
     first_sample = first_sample % samples_per_frame;
 
     /* parse frame L/R header (repeated at 0x03/04) */
-    frame_offset = stream->offset + bytes_per_frame*frames_in;
-    coef_index   = ((uint8_t)read_8bit(frame_offset+channel,stream->streamfile) >> 4) & 0xf;
-    shift_factor = ((uint8_t)read_8bit(frame_offset+channel,stream->streamfile) >> 0) & 0xf;
+    frame_offset = stream->offset + bytes_per_frame * frames_in;
+    read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
+    index = (frame[channel] >> 4) & 0xf;
+    shift = (frame[channel] >> 0) & 0xf;
+    coef1 = dtk_coefs[index][0];
+    coef2 = dtk_coefs[index][1];
     /* rare but happens, also repeated headers don't match (ex. Ikaruga (GC) SONG02.adp) */
-    VGM_ASSERT_ONCE(coef_index > 4 || shift_factor > 12, "DTK: incorrect coefs/shift at %x\n", (uint32_t)frame_offset);
+    VGM_ASSERT_ONCE(index > 4 || shift > 12, "DTK: incorrect coefs/shift at %x\n", (uint32_t)frame_offset);
 
     /* decode nibbles */
-    for (i = first_sample; i < first_sample+samples_to_do; i++) {
-        int32_t hist = 0, new_sample;
-        uint8_t nibbles = (uint8_t)read_8bit(frame_offset+0x04+i,stream->streamfile);
+    for (i = first_sample; i < first_sample + samples_to_do; i++) {
+        int sample, hist;
+        uint8_t nibbles = frame[0x04 + i];
 
-        /* apply XA filters << 6 */
-        switch(coef_index) {
-            case 0:
-                hist = 0; // (hist1 * 0) - (hist2 * 0);
-                break;
-            case 1:
-                hist = (hist1 * 60); // - (hist2 * 0);
-                break;
-            case 2:
-                hist = (hist1 * 115) - (hist2 * 52);
-                break;
-            case 3:
-                hist = (hist1 * 98) - (hist2 * 55);
-                break;
-        }
-        hist = (hist + 32) >> 6;
-        if (hist >  0x1fffff) hist =  0x1fffff;
-        if (hist < -0x200000) hist = -0x200000;
+        hist = (hist1*coef1 - hist2*coef2 + 32) >> 6;
+        if (hist > 2097151) hist = 2097151;
+        else if (hist < -2097152) hist = -2097152;
 
-        new_sample = (channel==0) ? /* L=low nibble first */
+        sample = (channel==0) ? /* L=low nibble first */
                 get_low_nibble_signed(nibbles) :
                 get_high_nibble_signed(nibbles);
-        new_sample = (new_sample << 12) >> shift_factor;
-        new_sample = (new_sample << 6) + hist;
+        sample = (sample << 12) >> shift;
+        sample = (sample << 6) + hist;
 
         hist2 = hist1;
-        hist1 = new_sample;
+        hist1 = sample; /* clamp *after* this so hist goes pretty high */
 
-        outbuf[sample_count] = clamp16(new_sample >> 6);
+        outbuf[sample_count] = clamp16(sample >> 6);
         sample_count += channelspacing;
     }
 
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/pcm_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/pcm_decoder.c
index cd1b623e5..cb86e2d14 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/pcm_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/pcm_decoder.c
@@ -206,15 +206,11 @@ void decode_alaw(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
 
 void decode_pcmfloat(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian) {
     int i, sample_count;
-    int32_t (*read_32bit)(off_t,STREAMFILE*) = big_endian ? read_32bitBE : read_32bitLE;
+    float (*read_f32)(off_t,STREAMFILE*) = big_endian ? read_f32be : read_f32le;
 
     for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
-        uint32_t sample_int = read_32bit(stream->offset+i*4,stream->streamfile);
-        float* sample_float;
-        int sample_pcm;
-
-        sample_float = (float*)&sample_int;
-        sample_pcm = (int)floor((*sample_float) * 32767.f + .5f);
+        float sample_float = read_f32(stream->offset+i*4,stream->streamfile);
+        int sample_pcm = (int)floor(sample_float * 32767.f + .5f);
 
         outbuf[sample_count] = clamp16(sample_pcm);
     }
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ptadpcm_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ptadpcm_decoder.c
index e128433ce..792466489 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/ptadpcm_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/ptadpcm_decoder.c
@@ -1,54 +1,69 @@
 #include "coding.h"
 
 
-/* a somewhat IMA-like mix of step+next index all in one (maybe an array[][] originally?) */
-static const int32_t ptadpcm_table[(16+16)*16] = { /* 16 of (step+index) + 16 values in a nibble */
-        -14,  2,     -10,  2,      -7,  1,      -5,  1,      -3,  0,      -2,  0,      -1,  0,       0,  0,
-          0,  0,       1,  0,       2,  0,       3,  0,       5,  1,       7,  1,      10,  2,      14,  2,
-        -28,  3,     -20,  3,     -14,  2,     -10,  2,      -7,  1,      -5,  1,      -3,  1,      -1,  0,
-          1,  0,       3,  1,       5,  1,       7,  1,      10,  2,      14,  2,      20,  3,      28,  3,
-        -56,  4,     -40,  4,     -28,  3,     -20,  3,     -14,  2,     -10,  2,      -6,  2,      -2,  1,
-          2,  1,       6,  2,      10,  2,      14,  2,      20,  3,      28,  3,      40,  4,      56,  4,
-       -112,  5,     -80,  5,     -56,  4,     -40,  4,     -28,  3,     -20,  3,     -12,  3,      -4,  2,
-          4,  2,      12,  3,      20,  3,      28,  3,      40,  4,      56,  4,      80,  5,     112,  5,
-       -224,  6,    -160,  6,    -112,  5,     -80,  5,     -56,  4,     -40,  4,     -24,  4,      -8,  3,
-          8,  3,      24,  4,      40,  4,      56,  4,      80,  5,     112,  5,     160,  6,     224,  6,
-       -448,  7,    -320,  7,    -224,  6,    -160,  6,    -112,  5,     -80,  5,     -48,  5,     -16,  4,
-         16,  4,      48,  5,      80,  5,     112,  5,     160,  6,     224,  6,     320,  7,     448,  7,
-       -896,  8,    -640,  8,    -448,  7,    -320,  7,    -224,  6,    -160,  6,     -96,  6,     -32,  5,
-         32,  5,      96,  6,     160,  6,     224,  6,     320,  7,     448,  7,     640,  8,     896,  8,
-      -1792,  9,   -1280,  9,    -896,  8,    -640,  8,    -448,  7,    -320,  7,    -192,  7,     -64,  6,
-         64,  6,     192,  7,     320,  7,     448,  7,     640,  8,     896,  8,    1280,  9,    1792,  9,
-      -3584, 10,   -2560, 10,   -1792,  9,   -1280,  9,    -896,  8,    -640,  8,    -384,  8,    -128,  7,
-        128,  7,     384,  8,     640,  8,     896,  8,    1280,  9,    1792,  9,    2560, 10,    3584, 10,
-      -7168, 11,   -5120, 11,   -3584, 10,   -2560, 10,   -1792,  9,   -1280,  9,    -768,  9,    -256,  8,
-        256,  8,     768,  9,    1280,  9,    1792,  9,    2560, 10,    3584, 10,    5120, 11,    7168, 11,
-     -14336, 11,  -10240, 11,   -7168, 11,   -5120, 11,   -3584, 10,   -2560, 10,   -1536, 10,    -512,  9,
-        512,  9,    1536, 10,    2560, 10,    3584, 10,    5120, 11,    7168, 11,   10240, 11,   14336, 11,
-     -28672, 11,  -20480, 11,  -14336, 11,  -10240, 11,   -7168, 11,   -5120, 11,   -3072, 11,   -1024, 10,
-       1024, 10,    3072, 11,    5120, 11,    7168, 11,   10240, 11,   14336, 11,   20480, 11,   28672, 11,
-        /* rest is 0s */
+/* a somewhat IMA-like mix of pre-calculated [index][nibble][step,index] all in one */
+static const int32_t ptadpcm_table[16][16][2] = {
+    {
+        {   -14,  2},  {   -10,  2},  {    -7,  1},  {    -5,  1},   {   -3,  0},   {   -2,  0},   {   -1,  0},   {    0,  0},
+        {     0,  0},  {     1,  0},  {     2,  0},  {     3,  0},   {    5,  1},   {    7,  1},   {   10,  2},   {   14,  2},
+    }, {
+        {   -28,  3},  {   -20,  3},  {   -14,  2},  {   -10,  2},   {   -7,  1},   {   -5,  1},   {   -3,  1},   {   -1,  0},
+        {     1,  0},  {     3,  1},  {     5,  1},  {     7,  1},   {   10,  2},   {   14,  2},   {   20,  3},   {   28,  3},
+    }, {
+        {   -56,  4},  {   -40,  4},  {   -28,  3},  {   -20,  3},   {  -14,  2},   {  -10,  2},   {   -6,  2},   {   -2,  1},
+        {     2,  1},  {     6,  2},  {    10,  2},  {    14,  2},   {   20,  3},   {   28,  3},   {   40,  4},   {   56,  4},
+    }, {
+        {  -112,  5},  {   -80,  5},  {   -56,  4},  {   -40,  4},   {  -28,  3},   {  -20,  3},   {  -12,  3},   {   -4,  2},
+        {     4,  2},  {    12,  3},  {    20,  3},  {    28,  3},   {   40,  4},   {   56,  4},   {   80,  5},   {  112,  5},
+    }, {
+        {  -224,  6},  {  -160,  6},  {  -112,  5},  {   -80,  5},   {  -56,  4},   {  -40,  4},   {  -24,  4},   {   -8,  3},
+        {     8,  3},  {    24,  4},  {    40,  4},  {    56,  4},   {   80,  5},   {  112,  5},   {  160,  6},   {  224,  6},
+    }, {
+        {  -448,  7},  {  -320,  7},  {  -224,  6},  {  -160,  6},   { -112,  5},   {  -80,  5},   {  -48,  5},   {  -16,  4},
+        {    16,  4},  {    48,  5},  {    80,  5},  {   112,  5},   {  160,  6},   {  224,  6},   {  320,  7},   {  448,  7},
+    }, {
+        {  -896,  8},  {  -640,  8},  {  -448,  7},  {  -320,  7},   { -224,  6},   { -160,  6},   {  -96,  6},   {  -32,  5},
+        {    32,  5},  {    96,  6},  {   160,  6},  {   224,  6},   {  320,  7},   {  448,  7},   {  640,  8},   {  896,  8},
+    }, {
+        { -1792,  9},  { -1280,  9},  {  -896,  8},  {  -640,  8},   { -448,  7},   { -320,  7},   { -192,  7},   {  -64,  6},
+        {    64,  6},  {   192,  7},  {   320,  7},  {   448,  7},   {  640,  8},   {  896,  8},   { 1280,  9},   { 1792,  9},
+    }, {
+        { -3584, 10},  { -2560, 10},  { -1792,  9},  { -1280,  9},   { -896,  8},   { -640,  8},   { -384,  8},   { -128,  7},
+        {   128,  7},  {   384,  8},  {   640,  8},  {   896,  8},   { 1280,  9},   { 1792,  9},   { 2560, 10},   { 3584, 10},
+    }, {
+        { -7168, 11},  { -5120, 11},  { -3584, 10},  { -2560, 10},   {-1792,  9},   {-1280,  9},   { -768,  9},   { -256,  8},
+        {   256,  8},  {   768,  9},  {  1280,  9},  {  1792,  9},   { 2560, 10},   { 3584, 10},   { 5120, 11},   { 7168, 11},
+    }, {
+        {-14336, 11},  {-10240, 11},  { -7168, 11},  { -5120, 11},   {-3584, 10},   {-2560, 10},   {-1536, 10},   { -512,  9},
+        {   512,  9},  {  1536, 10},  {  2560, 10},  {  3584, 10},   { 5120, 11},   { 7168, 11},   {10240, 11},   {14336, 11},
+    },  {
+        {-28672, 11},  {-20480, 11},  {-14336, 11},  {-10240, 11},   {-7168, 11},   {-5120, 11},   {-3072, 11},   {-1024, 10},
+        {  1024, 10},  {  3072, 11},  {  5120, 11},  {  7168, 11},   {10240, 11},   {14336, 11},   {20480, 11},   {28672, 11},
+    },
+    /* rest is 0s (uses up to index 12) */
 };
 
 /* Platinum "PtADPCM" custom ADPCM for Wwise (reverse engineered from .exes). */
-void decode_ptadpcm(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) {
+void decode_ptadpcm(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) {
+    uint8_t frame[0x104] = {0};
     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;
-    int index, step;
+    int index, nibble, step;
 
     /* external interleave (variable size), mono */
     bytes_per_frame = frame_size;
     samples_per_frame = 2 + (frame_size - 0x05) * 2;
     frames_in = first_sample / samples_per_frame;
-    first_sample = 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);
-    index = (uint8_t)read_8bit(frame_offset+0x04,stream->streamfile);
+    read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
+    hist2 = get_s16le(frame + 0x00);
+    hist1 = get_s16le(frame + 0x02);
+    index = frame[0x04];
 
     VGM_ASSERT_ONCE(index > 12, "PTADPCM: incorrect index at %x\n", (uint32_t)frame_offset);
 
@@ -66,26 +81,25 @@ void decode_ptadpcm(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa
 
     /* 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+0x05 + i/2,stream->streamfile);
-        uint8_t nibble;
+        uint8_t nibbles = frame[0x05 + i/2];
+        int32_t sample;
 
         nibble = !(i&1) ? /* low nibble first */
                 (nibbles >> 0) & 0xF :
                 (nibbles >> 4) & 0xF;
 
-        step  = ptadpcm_table[2*(nibble + 16*index) + 0];
-        index = ptadpcm_table[2*(nibble + 16*index) + 1];
-        new_sample = clamp16(step + 2*hist1 - hist2);
+        step  = ptadpcm_table[index][nibble][0];
+        index = ptadpcm_table[index][nibble][1];
+        sample = clamp16(step + 2*hist1 - hist2);
 
         if (sample_count >= first_sample && samples_done < samples_to_do) {
-            outbuf[samples_done * channelspacing] = new_sample;
+            outbuf[samples_done * channelspacing] = sample;
             samples_done++;
         }
         sample_count++;
 
         hist2 = hist1;
-        hist1 = new_sample;
+        hist1 = sample;
     }
 
     //stream->adpcm_history1_32 = hist1;
diff --git a/Frameworks/vgmstream/vgmstream/src/coding/xmd_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/xmd_decoder.c
index a438cea5e..43c7ab258 100644
--- a/Frameworks/vgmstream/vgmstream/src/coding/xmd_decoder.c
+++ b/Frameworks/vgmstream/vgmstream/src/coding/xmd_decoder.c
@@ -3,25 +3,27 @@
 
 /* Decodes Konami XMD from Xbox games.
  * Algorithm reverse engineered from SH4/CV:CoD's xbe (byte-accurate). */
-void decode_xmd(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) {
+void decode_xmd(VGMSTREAMCHANNEL *stream, sample_t *outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size) {
+    uint8_t frame[0x15] = {0};
     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;
+    //first_sample = first_sample % samples_per_frame; /* for flat layout */
 
     /* 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 */
-
+    frame_offset = stream->offset + bytes_per_frame * frames_in;
+    read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
+    hist2 = get_s16le(frame + 0x00);
+    hist1 = get_s16le(frame + 0x02);
+    scale = get_u16le(frame + 0x04); /* scale doesn't go too high though */
 
     /* write header samples (needed) */
     if (sample_count >= first_sample && samples_done < samples_to_do) {
@@ -37,25 +39,25 @@ void decode_xmd(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing,
 
     /* 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);
+        uint8_t nibbles = frame[0x06 + i/2];
+        int32_t sample;
 
-        new_sample = i&1 ? /* low nibble first */
+        sample = i&1 ? /* low nibble first */
                 get_high_nibble_signed(nibbles):
                  get_low_nibble_signed(nibbles);
         /* Coefs are based on XA's filter 2 (using those creates hissing in some songs though)
          * ex. 1.796875 * (1 << 14) = 0x7300, -0.8125 * (1 << 14) = -0x3400 */
-        new_sample = (new_sample*(scale<<14) + (hist1*0x7298) - (hist2*0x3350)) >> 14;
+        sample = (sample*(scale<<14) + (hist1*0x7298) - (hist2*0x3350)) >> 14;
 
         //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;
+            outbuf[samples_done * channelspacing] = (int16_t)sample;
             samples_done++;
         }
         sample_count++;
 
         hist2 = hist1;
-        hist1 = new_sample;
+        hist1 = sample;
     }
 
     //stream->adpcm_history1_32 = hist1;
diff --git a/Frameworks/vgmstream/vgmstream/src/formats.c b/Frameworks/vgmstream/vgmstream/src/formats.c
index d59e1dca4..ee93d9054 100644
--- a/Frameworks/vgmstream/vgmstream/src/formats.c
+++ b/Frameworks/vgmstream/vgmstream/src/formats.c
@@ -6,10 +6,16 @@
  * to inform plugins that need it. Common extensions are commented out to avoid stealing them
  * and possibly adding an unwanted association to the player. */
 
+/* Common extensions (like .wav or .ogg) should go in the common_extension_list. It should only
+ * contain common formats that vgmstream can also parse, to avoid hijacking them (since their
+ * plugins typically are faster and have desirable features vgmstream won't handle). Extensions of
+ * formats not parsed don't need to go there (for example .stm is a Scream Tracker Module elsewhere,
+ * but our .stm is very different so there is no conflict). */
+
 /* Some extensions require external libraries and could be #ifdef, not worth. */
 
 /* Formats marked as "not parsed" mean they'll go through FFmpeg, the header/extension isn't
- * parsed by vgmstream and typically won't not be fully accurate. May have a .ext.pos pair for fun. */
+ * parsed by vgmstream and typically won't not be fully accurate. */
 
 
 static const char* extension_list[] = {
@@ -25,7 +31,6 @@ static const char* extension_list[] = {
 
     //"aac", //common
     "aa3", //FFmpeg/not parsed (ATRAC3/ATRAC3PLUS/MP3/LPCM/WMA)
-    "aaap",
     "aax",
     "abk",
     //"ac3", //common, FFmpeg/not parsed (AC3)
@@ -131,6 +136,7 @@ static const char* extension_list[] = {
     "data",
     "dax",
     "dbm",
+    "dct",
     "dcs",
     "ddsp",
     "de2",
@@ -175,7 +181,7 @@ static const char* extension_list[] = {
     "gin",
     "gms",
     "gsb",
-    //"gsf", //conflicts with GBA gsf plugins?
+    "gsf",
     "gtd",
     "gwm",
 
@@ -211,6 +217,7 @@ static const char* extension_list[] = {
     "imc",
     "int",
     "is14",
+    "isb",
     "isd",
     "isws",
     "itl",
@@ -268,6 +275,7 @@ static const char* extension_list[] = {
     "lwma", //fake extension for .wma, FFmpeg/not parsed
 
     "mab",
+    "mad",
     "map",
     "matx",
     "mc3",
@@ -444,7 +452,7 @@ static const char* extension_list[] = {
     "sss",
     "ster",
     "sth",
-    //"stm", //common
+    "stm",
     "stma", //fake extension/header id for .stm
     "str",
     "stream",
@@ -568,6 +576,7 @@ static const char* extension_list[] = {
     "ydsp",
     "ymf",
 
+    "zic",
     "zsd",
     "zsm",
     "zss",
@@ -585,7 +594,6 @@ static const char* common_extension_list[] = {
     "aiff", //common
     "bin", //common
     "flac", //common
-    "gsf", //conflicts with GBA gsf plugins?
     "mp+", //common [Moonshine Runners (PC)]
     "mp2", //common
     "mp3", //common
@@ -593,7 +601,6 @@ static const char* common_extension_list[] = {
     "mpc", //common
     "ogg", //common
     "opus", //common
-    "stm", //common
     "wav", //common
 };
 
@@ -830,7 +837,7 @@ static const meta_info meta_info_list[] = {
         {meta_RFRM,                 "Retro Studios RFRM header"},
         {meta_NGC_ADPDTK,           "Nintendo ADP raw header"},
         {meta_RSF,                  "Retro Studios RSF raw header"},
-        {meta_AFC,                  "Nintendo AFC header"},
+        {meta_AFC,                  "Nintendo .AFC header"},
         {meta_AST,                  "Nintendo AST header"},
         {meta_HALPST,               "HAL Laboratory HALPST header"},
         {meta_DSP_RS03,             "Retro Studios RS03 header"},
@@ -864,6 +871,7 @@ static const meta_info meta_info_list[] = {
         {meta_PS2_VAGi,             "Sony VAGi header"},
         {meta_PS2_VAGp,             "Sony VAGp header"},
         {meta_PS2_pGAV,             "Sony pGAV header"},
+        {meta_PS2_VAGp_AAAP,        "Acclaim Austin AAAp VAG header"},
         {meta_SEB,                  "Game Arts .SEB header"},
         {meta_STR_WAV,              "Blitz Games .STR+WAV header"},
         {meta_PS2_ILD,              "ILD header"},
@@ -1021,7 +1029,6 @@ static const meta_info meta_info_list[] = {
         {meta_WII_BNS,              "Nintendo BNS header"},
         {meta_WII_WAS,              "Sumo Digital iSWS header"},
         {meta_XBOX_HLWAV,           "Half Life 2 bgm header"},
-        {meta_STX,                  "Nintendo .stx header"},
         {meta_MYSPD,                "U-Sing .MYSPD header"},
         {meta_HIS,                  "Her Interactive HIS header"},
         {meta_PS2_AST,              "KOEI AST header"},
@@ -1029,7 +1036,7 @@ static const meta_info meta_info_list[] = {
         {meta_DMSG,                 "RIFF/DMSGsegh header"},
         {meta_PONA_3DO,             "Policenauts BGM header"},
         {meta_PONA_PSX,             "Policenauts BGM header"},
-        {meta_NGC_DSP_AAAP,         "Acclaim Austin AAAp header"},
+        {meta_NGC_DSP_AAAP,         "Acclaim Austin AAAp DSP header"},
         {meta_NGC_DSP_KONAMI,       "Konami DSP header"},
         {meta_PS2_STER,             "STER Header"},
         {meta_BNSF,                 "Namco Bandai BNSF header"},
@@ -1240,6 +1247,8 @@ static const meta_info meta_info_list[] = {
         {meta_XMV_VALVE,            "Valve XMV header"},
         {meta_UBI_HX,               "Ubisoft HXx header"},
         {meta_BMP_KONAMI,           "Konami BMP header"},
+        {meta_ISB,                  "Creative ISACT header"},
+        {meta_XSSB,                 "Artoon XSSB header"},
 
 };
 
diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_1snh.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_1snh.c
index d784c2fc0..5e34c6158 100644
--- a/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_1snh.c
+++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_1snh.c
@@ -6,89 +6,81 @@
 void block_update_ea_1snh(off_t block_offset, VGMSTREAM * vgmstream) {
     STREAMFILE* streamFile = vgmstream->ch[0].streamfile;
     int i;
-    size_t block_size = 0, block_header = 0;
+    uint32_t block_id;
+    size_t block_size = 0, block_header = 0, audio_size = 0;
     int32_t (*read_32bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_32bitBE : read_32bitLE;
-    size_t file_size = get_streamfile_size(streamFile);
 
 
     /* EOF reads: signal we have nothing and let the layout fail */
-    if (block_offset >= file_size) {
+    if (block_offset >= get_streamfile_size(streamFile)) {
         vgmstream->current_block_offset = block_offset;
         vgmstream->next_block_offset = block_offset;
         vgmstream->current_block_samples = -1;
         return;
     }
 
+    block_id = read_32bitBE(block_offset + 0x00, streamFile);
 
-    while (block_offset < file_size) {
-        uint32_t id = read_32bitBE(block_offset+0x00,streamFile);
+    /* BE in SAT, but one file may have both BE and LE chunks [FIFA 98 (SAT): movie LE, audio BE] */
+    if (guess_endianness32bit(block_offset + 0x04, streamFile))
+        block_size = read_32bitBE(block_offset + 0x04, streamFile);
+    else
+        block_size = read_32bitLE(block_offset + 0x04, streamFile);
 
-        /* BE in SAT, but one file may have both BE and LE chunks [FIFA 98 (SAT): movie LE, audio BE] */
-        if (guess_endianness32bit(block_offset+0x04,streamFile))
-            block_size = read_32bitBE(block_offset+0x04,streamFile);
-        else
-            block_size = read_32bitLE(block_offset+0x04,streamFile);
+    block_header = 0;
 
-        block_header = 0;
+    if (block_id == 0x31534E68 || block_id == 0x53454144) {  /* "1SNh" "SEAD" audio header */
+        int is_sead = (block_id == 0x53454144);
+        int is_eacs = read_32bitBE(block_offset + 0x08, streamFile) == 0x45414353;
+        int is_zero = read_32bitBE(block_offset + 0x08, streamFile) == 0x00;
 
-        if (id == 0x31534E68 || id == 0x53454144) {  /* "1SNh" "SEAD" audio header */
-            int is_sead = (id == 0x53454144);
-            int is_eacs = read_32bitBE(block_offset+0x08, streamFile) == 0x45414353;
-
-            block_header = is_eacs ? 0x28 : (is_sead ? 0x14 : 0x2c);
-            if (block_header >= block_size) /* sometimes has audio data after header */
-                block_header = 0;
-        }
-        else if (id == 0x31534E64 || id == 0x534E4443) {  /* "1SNd" "SNDC" audio data */
-            block_header = 0x08;
-        }
-        else if (id == 0x00000000 || id == 0xFFFFFFFF || id == 0x31534E65) { /* EOF or "1SNe" */
-            vgmstream->current_block_samples = -1;
-            break;
-        }
-
-        if (block_header) {
-            break;
-        }
-
-        block_offset += block_size;
+        block_header = (is_eacs || is_zero) ? 0x28 : (is_sead ? 0x14 : 0x2c);
+        if (block_header >= block_size) /* sometimes has audio data after header */
+            block_header = 0;
+    } else if (block_id == 0x31534E64 || block_id == 0x534E4443) {  /* "1SNd" "SNDC" audio data */
+        block_header = 0x08;
+    } else if (block_id == 0x00000000 || block_id == 0xFFFFFFFF || block_id == 0x31534E65) { /* EOF or "1SNe" */
+        vgmstream->current_block_samples = -1;
+        return;
     }
 
     vgmstream->current_block_offset = block_offset;
     vgmstream->next_block_offset    = block_offset + block_size;
-    vgmstream->current_block_size   = block_size - block_header;
-    if (vgmstream->current_block_samples == -1)
+    if (block_header == 0) {
+        /* no audio data, skip this block */
+        vgmstream->current_block_samples = 0;
         return;
+    }
 
+    audio_size = block_size - block_header;
 
     /* set new channel offsets and block sizes */
     switch(vgmstream->coding_type) {
         case coding_PCM8_int:
         case coding_ULAW_int:
-            vgmstream->current_block_size /= vgmstream->channels;
+            vgmstream->current_block_samples = pcm_bytes_to_samples(audio_size, vgmstream->channels, 8);
             for (i=0;i<vgmstream->channels;i++) {
                 vgmstream->ch[i].offset = block_offset + block_header + i;
             }
             break;
 
         case coding_PCM16_int:
-            vgmstream->current_block_size /= vgmstream->channels;
+            vgmstream->current_block_samples = pcm_bytes_to_samples(audio_size, vgmstream->channels, 16);
             for (i=0;i<vgmstream->channels;i++) {
                 vgmstream->ch[i].offset = block_offset + block_header + (i*2);
             }
             break;
 
         case coding_PSX:
-            vgmstream->current_block_size /= vgmstream->channels;
+            vgmstream->current_block_samples = ps_bytes_to_samples(audio_size, vgmstream->channels);
             for (i=0;i<vgmstream->channels;i++) {
-                vgmstream->ch[i].offset = block_offset + block_header + i*vgmstream->current_block_size;
+                vgmstream->ch[i].offset = block_offset + block_header + i*(audio_size/vgmstream->channels);
             }
             break;
 
         case coding_DVI_IMA:
             if (vgmstream->codec_config == 1) { /* ADPCM hist */
                 vgmstream->current_block_samples = read_32bit(block_offset + block_header, streamFile);
-                vgmstream->current_block_size = 0; // - (0x04 + 0x08*vgmstream->channels); /* should be equivalent */
 
                 for(i = 0; i < vgmstream->channels; i++) {
                     off_t adpcm_offset = block_offset + block_header + 0x04;
@@ -101,6 +93,7 @@ void block_update_ea_1snh(off_t block_offset, VGMSTREAM * vgmstream) {
                 //           "EA 1SHN blocked: different expected vs block num samples at %lx\n", block_offset);
             }
             else {
+                vgmstream->current_block_samples = ima_bytes_to_samples(audio_size, vgmstream->channels);
                 for(i = 0; i < vgmstream->channels; i++) {
                     vgmstream->ch[i].offset = block_offset + block_header;
                 }
diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_schl.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_schl.c
index 7b4b024e4..22d3431e0 100644
--- a/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_schl.c
+++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_schl.c
@@ -6,7 +6,6 @@
 void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) {
     STREAMFILE* streamFile = vgmstream->ch[0].streamfile;
     int i;
-    int new_schl = 0;
     size_t block_size, block_samples;
     int32_t (*read_32bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_32bitBE : read_32bitLE;
 
@@ -44,9 +43,21 @@ void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) {
             block_samples = 0; /* layout ignores this */
         }
 
-        /* "SCHl" start block (movie "SHxx" shouldn't use multi files) */
-        if (block_id == 0x5343486C)
-            new_schl = 1;
+        /* "SCHl" start block, when decoding multi files pasted together */
+        if (block_id == 0x5343486C) {
+            switch(vgmstream->coding_type) {
+                case coding_MPEG_custom:
+                case coding_MPEG_layer1:
+                case coding_MPEG_layer2:
+                case coding_MPEG_layer3:
+                case coding_MPEG_ealayer3:
+                    /* need to reset MPEG decoder to reset discards and trailing samples in the buffers */
+                    flush_mpeg(vgmstream->codec_data);
+                    break;
+                default:
+                    break;
+            }
+        }
 
         /* padding between "SCEl" and next "SCHl" (when subfiles exist) */
         if (block_id == 0x00000000)
@@ -159,12 +170,6 @@ void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) {
 
                 vgmstream->ch[i].offset = block_offset + 0x0C + (0x04*vgmstream->channels) + channel_start;
             }
-
-            /* SCHl with multiple SCHl need to reset their MPEG decoder as there are trailing samples in the buffers */
-            if (new_schl) {
-                flush_mpeg(vgmstream->codec_data);
-            }
-
             break;
 #endif
         /* id, size, samples, offsets-per-channel, interleaved data (w/ optional hist per channel) */
diff --git a/Frameworks/vgmstream/vgmstream/src/layout/segmented.c b/Frameworks/vgmstream/vgmstream/src/layout/segmented.c
index 1b4c953ed..7965dc9b1 100644
--- a/Frameworks/vgmstream/vgmstream/src/layout/segmented.c
+++ b/Frameworks/vgmstream/vgmstream/src/layout/segmented.c
@@ -2,7 +2,7 @@
 #include "../vgmstream.h"
 #include "../mixing.h"
 
-#define VGMSTREAM_MAX_SEGMENTS 512
+#define VGMSTREAM_MAX_SEGMENTS 1024
 #define VGMSTREAM_SEGMENT_SAMPLE_BUFFER 8192
 
 
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/adx.c b/Frameworks/vgmstream/vgmstream/src/meta/adx.c
index 5252b641b..a42c26971 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/adx.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/adx.c
@@ -3,14 +3,15 @@
 #endif
 #include <math.h>
 #include <limits.h>
-
 #include "meta.h"
 #include "adx_keys.h"
 #include "../coding/coding.h"
 
-#define MAX_TEST_FRAMES (INT_MAX/0x8000)
 
-static int find_adx_key(STREAMFILE *streamFile, uint8_t type, uint16_t *xor_start, uint16_t *xor_mult, uint16_t *xor_add);
+#define ADX_KEY_MAX_TEST_FRAMES 32768
+#define ADX_KEY_TEST_BUFFER_SIZE 0x8000
+
+static int find_adx_key(STREAMFILE *sf, uint8_t type, uint16_t *xor_start, uint16_t *xor_mult, uint16_t *xor_add);
 
 /* ADX - CRI Middleware format */
 VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) {
@@ -18,7 +19,7 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) {
     off_t start_offset, hist_offset = 0;
     int loop_flag = 0, channel_count;
     int32_t loop_start_sample = 0, loop_end_sample = 0;
-    uint16_t version_signature;
+    uint16_t version;
     uint8_t encoding_type;
     uint8_t frame_size;
 
@@ -29,79 +30,77 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) {
 
 
     /* checks*/
-    /* .adx: standard, .adp: Headhunter (DC) */
+    /* .adx: standard
+     * .adp: Headhunter (DC) */
     if (!check_extensions(streamFile,"adx,adp"))
         goto fail;
 
-    /* check first 2 bytes */
-    if ((uint16_t)read_16bitBE(0x00,streamFile)!=0x8000) goto fail;
+    if ((uint16_t)read_16bitBE(0x00,streamFile) != 0x8000)
+        goto fail;
 
-    /* get stream offset, check for CRI signature just before */
-    start_offset = (uint16_t)read_16bitBE(0x02,streamFile) + 4;
-    if ((uint16_t)read_16bitBE(start_offset-6,streamFile)!=0x2863 ||   /* "(c" */
-        (uint32_t)read_32bitBE(start_offset-4,streamFile)!=0x29435249  /* ")CRI" */
-       ) goto fail;
+    start_offset = (uint16_t)read_16bitBE(0x02,streamFile) + 0x04;
+    if ((uint16_t)read_16bitBE(start_offset - 0x06,streamFile) != 0x2863 ||   /* "(c" */
+        (uint32_t)read_32bitBE(start_offset - 0x04,streamFile) != 0x29435249) /* ")CRI" */
+        goto fail;
 
-    /* check for encoding type */
-    /* 0x02 is for some unknown fixed filter, 0x03 is standard ADX, 0x04 is
-     * ADX with exponential scale, 0x10 is AHX for DC, 0x11 is AHX */
     encoding_type = read_8bit(0x04, streamFile);
-
     switch (encoding_type) {
-        case 2:
+        case 0x02:
             coding_type = coding_CRI_ADX_fixed;
             break;
-        case 3:
+        case 0x03:
             coding_type = coding_CRI_ADX;
             break;
-        case 4:
+        case 0x04:
             coding_type = coding_CRI_ADX_exp;
             break;
-        default:
+        default: /* 0x10 is AHX for DC, 0x11 is AHX */
             goto fail;
     }
 
+    /* ADX encoders can't set this value, but is honored by ADXPlay if changed and multiple of 0x12,
+     * though output is unusual and may not be fully supported (works in mono so not an interleave) */
     frame_size = read_8bit(0x05, streamFile);
 
-    /* check for bits per sample? (only 4 makes sense for ADX) */
-    if (read_8bit(0x06,streamFile) != 4) goto fail;
+    if (read_8bit(0x06,streamFile) != 4) /* bits per sample */
+        goto fail;
 
     /* older ADX (adxencd) up to 2ch, newer ADX (criatomencd) up to 8 */
     channel_count = read_8bit(0x07,streamFile);
+    /* 0x08: sample rate */
+    /* 0x0c: samples */
+    /* 0x10: high-pass frequency */
 
-    /* check version signature, read loop info */
-    version_signature = read_16bitBE(0x12,streamFile);
-
+    version = read_16bitBE(0x12,streamFile);
 
     /* encryption */
-    if (version_signature == 0x0408) {
+    if (version == 0x0408) {
         if (find_adx_key(streamFile, 8, &xor_start, &xor_mult, &xor_add)) {
             coding_type = coding_CRI_ADX_enc_8;
-            version_signature = 0x0400;
+            version = 0x0400;
         }
     }
-    else if (version_signature == 0x0409) {
+    else if (version == 0x0409) {
         if (find_adx_key(streamFile, 9, &xor_start, &xor_mult, &xor_add)) {
             coding_type = coding_CRI_ADX_enc_9;
-            version_signature = 0x0400;
+            version = 0x0400;
         }
     }
 
-
     /* version + extra data */
-    if (version_signature == 0x0300) {  /* early ADX (~2004?) */
+    if (version == 0x0300) {  /* early ADX (~1998) [Grandia (SAT), Baroque (SAT)] */
         size_t base_size = 0x14, loops_size = 0x18;
 
         header_type = meta_ADX_03;
 
         /* no sample history */
 
-        if (start_offset - 6 >= base_size + loops_size) { /* enough space for loop info? */
+        if (start_offset - 0x06 >= base_size + loops_size) { /* enough space for loop info? */
             off_t loops_offset = base_size;
 
-            /* off+0x00 (2): initial loop padding (the encoder adds a few blank samples so loop start is block-aligned; max 31)
+            /* 0x00 (2): initial loop padding (the encoder adds a few blank samples so loop start is block-aligned; max 31)
              *  ex. loop_start=12: enc_start=32, padding=20 (32-20=12); loop_start=35: enc_start=64, padding=29 (64-29=35)
-             * off+0x02 (2): loop sample(?) flag (always 1) */
+             * 0x02 (2): loop sample(?) flag (always 1) */
             loop_flag           = read_32bitBE(loops_offset+0x04,streamFile) != 0; /* loop offset(?) flag (always 1) */
             loop_start_sample   = read_32bitBE(loops_offset+0x08,streamFile);
             //loop_start_offset = read_32bitBE(loops_offset+0x0c,streamFile);
@@ -109,37 +108,40 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) {
             //loop_end_offset   = read_32bitBE(loops_offset+0x14,streamFile);
         }
     }
-    else if (version_signature == 0x0400) {  /* common */
+    else if (version == 0x0400) {  /* common */
         size_t base_size = 0x18, hist_size, ainf_size = 0, loops_size = 0x18;
         off_t ainf_offset;
 
         header_type = meta_ADX_04;
 
         hist_offset = base_size; /* always present but often blank */
-        hist_size = (channel_count > 1 ? 4*channel_count : 4+4); /* min is 8, even in 1ch files */
+        hist_size = (channel_count > 1 ? 0x04 * channel_count : 0x04 + 0x04); /* min is 8, even in 1ch files */
 
-        ainf_offset = base_size + hist_size + 0x4; /* not seen with >2ch though */
+        ainf_offset = base_size + hist_size + 0x04; /* not seen with >2ch though */
         if ((uint32_t)read_32bitBE(ainf_offset+0x00,streamFile) == 0x41494E46) /* "AINF" */
             ainf_size = read_32bitBE(ainf_offset+0x04,streamFile);
 
-        if (start_offset - ainf_size - 6 >= hist_offset + hist_size + loops_size) {  /* enough space for loop info? */
+        if (start_offset - ainf_size - 0x06 >= hist_offset + hist_size + loops_size) {  /* enough space for loop info? */
             off_t loops_offset = base_size + hist_size;
 
-            /* off+0x00 (2): initial loop padding (the encoder adds a few blank samples so loop start is block-aligned; max 31)
+            /* 0x00 (2): initial loop padding (the encoder adds a few blank samples so loop start is block-aligned; max 31)
              *  ex. loop_start=12: enc_start=32, padding=20 (32-20=12); loop_start=35: enc_start=64, padding=29 (64-29=35)
-             * off+0x02 (2): loop sample(?) flag (always 1) */
+             * 0x02 (2): loop sample(?) flag (always 1) */
             loop_flag           = read_32bitBE(loops_offset+0x04,streamFile) != 0; /* loop offset(?) flag (always 1) */
             loop_start_sample   = read_32bitBE(loops_offset+0x08,streamFile);
-            //loop_start_offset = read_32bitBE(loops_offset+0x0c,streamFile);
+          //loop_start_offset   = read_32bitBE(loops_offset+0x0c,streamFile);
             loop_end_sample     = read_32bitBE(loops_offset+0x10,streamFile);
-            //loop_end_offset   = read_32bitBE(loops_offset+0x14,streamFile);
+          //loop_end_offset     = read_32bitBE(loops_offset+0x14,streamFile);
         }
 
         /* AINF header info (may be inserted by CRI's tools but is rarely used)
          *  Can also start right after the loop points (base_size + hist_size + loops_size)
-         * 0x00 (4): "AINF";  0x04 (4): size;  0x08 (10): str_id
+         * 0x00 (4): "AINF"
+         * 0x04 (4): size
+         * 0x08 (10): str_id
          * 0x18 (2): volume (0=base/max?, negative=reduce)
-         * 0x1c (2): pan l;   0x1e (2): pan r (0=base, max +-128) */
+         * 0x1c (2): pan l
+         * 0x1e (2): pan r (0=base, max +-128) */
 
         /* CINF header info (very rare, found after loops) [Sakura Taisen 3 (PS2)]
          * 0x00 (4): "CINF"
@@ -149,7 +151,7 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) {
          * 0x48 (-): file name, null terminated
          */
     }
-    else if (version_signature == 0x0500) {  /* found in some SFD: Buggy Heat, appears to have no loop */
+    else if (version == 0x0500) {  /* found in some SFD: Buggy Heat, appears to have no loop */
         header_type = meta_ADX_05;
     }
     else { /* not a known/supported version signature */
@@ -161,13 +163,13 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) {
     vgmstream = allocate_vgmstream(channel_count,loop_flag);
     if (!vgmstream) goto fail;
 
-    vgmstream->num_samples = read_32bitBE(0xc,streamFile);
-    vgmstream->sample_rate = read_32bitBE(0x8,streamFile);
+    vgmstream->sample_rate = read_32bitBE(0x08,streamFile);
+    vgmstream->num_samples = read_32bitBE(0x0c,streamFile);
     vgmstream->loop_start_sample = loop_start_sample;
     vgmstream->loop_end_sample = loop_end_sample;
 
     vgmstream->coding_type = coding_type;
-    vgmstream->layout_type = channel_count==1 ? layout_none : layout_interleave;
+    vgmstream->layout_type = layout_interleave;
     vgmstream->interleave_block_size = frame_size;
     vgmstream->meta_type = header_type;
 
@@ -175,6 +177,7 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) {
     /* calculate filter coefficients */
     if (coding_type == coding_CRI_ADX_fixed) {
         int i;
+        /* standard XA coefs * (2<<11) */
         for (i = 0; i < channel_count; i++) {
             vgmstream->ch[i].adpcm_coef[0] = 0x0000;
             vgmstream->ch[i].adpcm_coef[1] = 0x0000;
@@ -194,14 +197,14 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) {
 
         x = cutoff;
         y = vgmstream->sample_rate;
-        z = cos(2.0*M_PI*x/y);
+        z = cos(2.0 * M_PI * x / y);
 
-        a = M_SQRT2-z;
-        b = M_SQRT2-1.0;
-        c = (a-sqrt((a+b)*(a-b)))/b;
+        a = M_SQRT2 - z;
+        b = M_SQRT2 - 1.0;
+        c = (a - sqrt((a + b) * (a - b))) / b;
 
-        coef1 = (short)(c*8192);
-        coef2 = (short)(c*c*-4096);
+        coef1 = (short)(c * 8192);
+        coef2 = (short)(c * c * -4096);
 
         for (i = 0; i < channel_count; i++) {
             vgmstream->ch[i].adpcm_coef[0] = coef1;
@@ -213,7 +216,7 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) {
     {
         int i;
 
-        for (i=0;i<channel_count;i++) {
+        for (i = 0; i < channel_count; i++) {
             /* 2 hist shorts per ch, corresponding to the very first original sample repeated (verified with CRI's encoders).
              * Not vital as their effect is small, after a few samples they don't matter, and most songs start in silence. */
             if (hist_offset) {
@@ -228,7 +231,7 @@ VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile) {
                 vgmstream->ch[i].adx_mult = xor_mult;
                 vgmstream->ch[i].adx_add = xor_add;
 
-                for (j=0;j<i;j++)
+                for (j = 0; j < i; j++)
                     adx_next_key(&vgmstream->ch[i]);
             }
         }
@@ -245,12 +248,14 @@ fail:
 }
 
 
-/* return 0 if not found, 1 if found and set parameters */
-static int find_adx_key(STREAMFILE *streamFile, uint8_t type, uint16_t *xor_start, uint16_t *xor_mult, uint16_t *xor_add) {
-    uint16_t * scales = NULL;
-    uint16_t * prescales = NULL;
-    int bruteframe = 0, bruteframe_count = -1;
-    int startoff, endoff;
+/* ADX key detection works by reading XORed ADPCM scales in frames, and un-XORing with keys in
+ * a list. If resulting values are within the expected range for N scales we accept that key. */
+static int find_adx_key(STREAMFILE *sf, uint8_t type, uint16_t *xor_start, uint16_t *xor_mult, uint16_t *xor_add) {
+    const int frame_size = 0x12;
+    uint16_t *scales = NULL;
+    uint16_t *prescales = NULL;
+    int bruteframe_start = 0, bruteframe_count = -1;
+    off_t start_offset;
     int i, rc = 0;
 
 
@@ -260,7 +265,7 @@ static int find_adx_key(STREAMFILE *streamFile, uint8_t type, uint16_t *xor_star
         size_t key_size;
 
         /* handle type8 keystrings, key9 keycodes and derived keys too */
-        key_size = read_key_file(keybuf,0x20, streamFile);
+        key_size = read_key_file(keybuf,0x20, sf);
 
         if (key_size > 0) {
             int i, is_ascii = 0;
@@ -299,77 +304,90 @@ static int find_adx_key(STREAMFILE *streamFile, uint8_t type, uint16_t *xor_star
     /* setup totals */
     {
         int frame_count;
+        int channels = read_8bit(0x07, sf);
+        int num_samples = read_32bitBE(0x0c, sf);
+        off_t end_offset;
 
-        startoff = read_16bitBE(2, streamFile) + 4;
-        endoff = (read_32bitBE(12, streamFile) + 31) / 32 * 18 * read_8bit(7, streamFile) + startoff;
+        start_offset = read_16bitBE(0x02, sf) + 0x4;
+        end_offset = (num_samples + 31) / 32 * frame_size * channels + start_offset; /* samples-to-bytes */
 
-        frame_count = (endoff - startoff) / 18;
+        frame_count = (end_offset - start_offset) / frame_size;
         if (frame_count < bruteframe_count || bruteframe_count < 0)
             bruteframe_count = frame_count;
     }
 
-    /* find longest run of nonzero frames */
+    /* find longest run of non-zero frames (zero frames aren't good for key testing) */
     {
-        int longest = -1, longest_length = -1;
-        int length = 0;
+        static const uint8_t zeroes[0x12] = {0};
+        uint8_t frame[0x12];
+        int longest_start = -1, longest_count = -1;
+        int count = 0;
+
         for (i = 0; i < bruteframe_count; i++) {
-            static const unsigned char zeroes[18] = {0};
-            unsigned char buf[18];
-            read_streamfile(buf, startoff + i * 18, 18, streamFile);
-            if (memcmp(zeroes, buf, 18))
-                length++;
+            read_streamfile(frame, start_offset + i*frame_size, frame_size, sf);
+            if (memcmp(zeroes, frame, frame_size) != 0)
+                count++;
             else
-                length = 0;
-            if (length > longest_length) {
-                longest_length = length;
-                longest = i - length + 1;
-                if (longest_length >= 0x8000)
+                count = 0;
+
+            /* update new record of non-zero frames */
+            if (count > longest_count) {
+                longest_count = count;
+                longest_start = i - count + 1;
+                if (longest_count >= ADX_KEY_MAX_TEST_FRAMES)
                     break;
             }
         }
-        if (longest == -1) {
-            goto find_key_cleanup;
+
+        /* no non-zero frames */
+        if (longest_start == -1) {
+            goto done;
         }
-        bruteframe_count = longest_length;
-        bruteframe = longest;
+
+        bruteframe_start = longest_start;
+        bruteframe_count = longest_count;
+        if (bruteframe_count > ADX_KEY_MAX_TEST_FRAMES) //?
+            bruteframe_count = ADX_KEY_MAX_TEST_FRAMES;
     }
 
-    /* try to guess key */
+    /* pre-load scales in a table, to avoid re-reading them per key */
     {
-        const adxkey_info * keys = NULL;
-        int keycount = 0, keymask = 0;
-        int scales_to_do;
-        int key_id;
-
         /* allocate storage for scales */
-        scales_to_do = (bruteframe_count > MAX_TEST_FRAMES ? MAX_TEST_FRAMES : bruteframe_count);
-        scales = malloc(scales_to_do*sizeof(uint16_t));
-        if (!scales) goto find_key_cleanup;
+        scales = malloc(bruteframe_count * sizeof(uint16_t));
+        if (!scales) goto done;
 
-        /* prescales are those scales before the first frame we test
-         * against, we use these to compute the actual start */
-        if (bruteframe > 0) {
-            /* allocate memory for the prescales */
-            prescales = malloc(bruteframe*sizeof(uint16_t));
-            if (!prescales) goto find_key_cleanup;
+        /* prescales are scales before the first test frame, with some blank frames no good
+         * for key testing, but we must read to compute XOR value at bruteframe_start */
+        if (bruteframe_start > 0) {
+            /* allocate storage for prescales */
+            prescales = malloc(bruteframe_start * sizeof(uint16_t));
+            if (!prescales) goto done;
 
             /* read the prescales */
-            for (i=0; i<bruteframe; i++) {
-                prescales[i] = read_16bitBE(startoff+i*18, streamFile);
+            for (i = 0; i < bruteframe_start; i++) {
+                prescales[i] = read_16bitBE(start_offset + i*frame_size, sf);
             }
         }
 
         /* read in the scales */
-        for (i=0; i < scales_to_do; i++) {
-            scales[i] = read_16bitBE(startoff+(bruteframe+i)*18, streamFile);
+        for (i = 0; i < bruteframe_count; i++) {
+            scales[i] = read_16bitBE(start_offset + (bruteframe_start + i)*frame_size, sf);
         }
+    }
 
+    /* try to guess key */
+    {
+        const adxkey_info *keys = NULL;
+        int keycount = 0, keymask = 0;
+        int key_id;
+
+        /* setup test mask (used to check high bits that signal un-XORed scale would be too high to be valid) */
         if (type == 8) {
             keys = adxkey8_list;
             keycount = adxkey8_list_count;
             keymask = 0x6000;
         }
-        else if (type == 9) {
+        else { //if (type == 9)
             /* smarter XOR as seen in PSO2. The scale is technically 13 bits,
              * but the maximum value assigned by the encoder is 0x1000.
              * This is written to the ADX file as 0xFFF, leaving the high bit
@@ -379,7 +397,7 @@ static int find_adx_key(STREAMFILE *streamFile, uint8_t type, uint16_t *xor_star
             keymask = 0x1000;
         }
 
-        /* try all keys until one decrypts correctly vs expected values */
+        /* try all keys until one decrypts correctly vs expected scales */
         for (key_id = 0; key_id < keycount; key_id++) {
             uint16_t key_xor, key_mul, key_add;
             uint16_t xor, mul, add;
@@ -400,6 +418,7 @@ static int find_adx_key(STREAMFILE *streamFile, uint8_t type, uint16_t *xor_star
                 VGM_LOG("ADX: incorrectly defined key id=%i\n", key_id);
                 continue;
             }
+
             /* temp test values */
             xor = key_xor;
             mul = key_mul;
@@ -428,30 +447,34 @@ static int find_adx_key(STREAMFILE *streamFile, uint8_t type, uint16_t *xor_star
             }
 #endif
 
-
-            /* test vs prescales while xor looks valid */
-            for (i = 0; i < bruteframe && ((prescales[i] & keymask) == (xor & keymask) || prescales[i] == 0); i++) {
+            /* test vs prescales while XOR looks valid */
+            for (i = 0; i < bruteframe_start; i++) {
+                if ((prescales[i] & keymask) != (xor & keymask) && prescales[i] != 0)
+                    break;
                 xor = xor * mul + add;
             }
-            if (i == bruteframe) {
-                /* test vs scales while xor looks valid */
-                for (i = 0; i < scales_to_do && (scales[i] & keymask) == (xor & keymask); i++) {
-                    xor = xor * mul + add;
-                }
-                /* key is good */
-                if (i == scales_to_do) {
-                    *xor_start = key_xor;
-                    *xor_mult = key_mul;
-                    *xor_add = key_add;
+            if (i != bruteframe_start)
+                continue;
 
-                    rc = 1;
-                    goto find_key_cleanup;
-                }
+            /* test vs scales while XOR looks valid */
+            for (i = 0; i < bruteframe_count; i++) {
+                if ((scales[i] & keymask) != (xor & keymask))
+                    break;
+                xor = xor * mul + add;
             }
+            if (i != bruteframe_count)
+                continue;
+
+            /* all scales are valid, key is good */
+            *xor_start = key_xor;
+            *xor_mult = key_mul;
+            *xor_add = key_add;
+            rc = 1;
+            break;
         }
     }
 
-find_key_cleanup:
+done:
     free(scales);
     free(prescales);
     return rc;
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/afc.c b/Frameworks/vgmstream/vgmstream/src/meta/afc.c
index 54269fff4..94ecc7096 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/afc.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/afc.c
@@ -1,67 +1,53 @@
 #include "meta.h"
 #include "../util.h"
 
+
+/* .AFC - from Nintendo games [Super Mario Sunshine (GC), The Legend of Zelda: Wind Waker (GC)] */
 VGMSTREAM * init_vgmstream_afc(STREAMFILE *streamFile) {
     VGMSTREAM * vgmstream = NULL;
-    char filename[PATH_LIMIT];
+    off_t start_offset;
+    int loop_flag, channel_count;
 
-    int loop_flag;
-    const int channel_count = 2;    /* .afc seems to be stereo only */
 
-    /* check extension, case insensitive */
-    streamFile->get_name(streamFile,filename,sizeof(filename));
-    if (strcasecmp("afc",filename_extension(filename))) goto fail;
-
-    /* don't grab AIFF-C with .afc extension */
-    if ((uint32_t)read_32bitBE(0x0,streamFile)==0x464F524D) /* FORM */
+    /* checks */
+    /* .afc: common
+     * .stx: Pikmin (GC) */
+    if (!check_extensions(streamFile, "afc,stx"))
         goto fail;
 
-    /* we will get a sample rate, that's as close to checking as I think
-     * we can get */
+    if (read_u32be(0x00, streamFile) > get_streamfile_size(streamFile)) /* size without padding */
+        goto fail;
+
+    if (read_u16be(0x0a, streamFile) != 4) /* bps? */
+        goto fail;
+    if (read_u16be(0x0c, streamFile) != 16) /* samples per frame? */
+        goto fail;
+    /* 0x0e: always 0x1E? */
+
+    channel_count = 2;
+    loop_flag = read_s32be(0x10, streamFile);
+    start_offset = 0x20;
+
 
     /* build the VGMSTREAM */
-
-    loop_flag = read_32bitBE(0x10,streamFile);
-
     vgmstream = allocate_vgmstream(channel_count,loop_flag);
     if (!vgmstream) goto fail;
 
-    /* fill in the vital statistics */
-    vgmstream->num_samples = read_32bitBE(0x04,streamFile);
-    vgmstream->sample_rate = (uint16_t)read_16bitBE(0x08,streamFile);
-    /* channels and loop flag are set by allocate_vgmstream */
-    vgmstream->loop_start_sample = read_32bitBE(0x14,streamFile);
+    vgmstream->meta_type = meta_AFC;
+    vgmstream->num_samples = read_s32be(0x04, streamFile);
+    vgmstream->sample_rate = read_u16be(0x08, streamFile);
+    vgmstream->loop_start_sample = read_s32be(0x14, streamFile);
     vgmstream->loop_end_sample = vgmstream->num_samples;
 
     vgmstream->coding_type = coding_NGC_AFC;
     vgmstream->layout_type = layout_interleave;
-    vgmstream->meta_type = meta_AFC;
-
-    /* frame-level interleave (9 bytes) */
-    vgmstream->interleave_block_size = 9;
-
-    /* open the file for reading by each channel */
-    {
-        STREAMFILE *chstreamfile;
-        int i;
-
-        /* both channels use same buffer, as interleave is so small */
-        chstreamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
-        if (!chstreamfile) goto fail;
-
-        for (i=0;i<channel_count;i++) {
-            vgmstream->ch[i].streamfile = chstreamfile;
-
-            vgmstream->ch[i].channel_start_offset=
-                vgmstream->ch[i].offset=
-                0x20 + i*vgmstream->interleave_block_size;
-        }
-    }
+    vgmstream->interleave_block_size = 0x09;
 
+    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;
 }
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_1snh.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_1snh.c
index 2ec1da29b..647496925 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/ea_1snh.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_1snh.c
@@ -30,40 +30,62 @@ typedef struct {
 static int parse_header(STREAMFILE* streamFile, ea_header* ea, off_t begin_offset);
 static VGMSTREAM * init_vgmstream_main(STREAMFILE *streamFile, ea_header* ea);
 
-static void set_ea_1snh_num_samples(STREAMFILE* streamFile, off_t start_offset, ea_header* ea);
+static void set_ea_1snh_num_samples(VGMSTREAM *vgmstream, STREAMFILE *streamFile, ea_header *ea, int find_loop);
 static int get_ea_1snh_ima_version(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea);
 
 /* EA 1SNh - from early EA games, stream (~1996, ex. Need for Speed) */
 VGMSTREAM * init_vgmstream_ea_1snh(STREAMFILE *streamFile) {
-    ea_header ea = {0};
-    off_t eacs_offset;
+    ea_header ea = { 0 };
+    off_t offset, eacs_offset;
+    VGMSTREAM *vgmstream = NULL;
 
 
     /* checks */
     /* .asf/as4: common,
      * .lasf: fake for plugins
-     * .cnk: some PS games
+     * .cnk: some PS1 games
      * .sng: fake for plugins (to mimic EA SCHl's common extension)
-     * .uv/tgq: some SAT games (video only?) */
-    if (!check_extensions(streamFile,"asf,lasf,as4,cnk,sng,uv,tgq"))
+     * .uv/tgq: some SAT games (video only?)
+     * .tgv: videos
+     * (extensionless): Need for Speed (SAT) (videos) */
+    if (!check_extensions(streamFile, "asf,lasf,as4,cnk,sng,uv,tgq,tgv,"))
         goto fail;
 
-    if (read_32bitBE(0x00,streamFile) != 0x31534E68 &&  /* "1SNh" */
-        read_32bitBE(0x00,streamFile) != 0x53454144)    /* "SEAD" */
+    offset = 0x00;
+
+    /* in TGV videos, either TGVk or 1SNh block comes first */
+    if (read_32bitBE(0x00, streamFile) == 0x5447566B) { /* "TGVk" */
+        offset = read_32bitBE(0x04, streamFile);
+    } else if (read_32bitLE(0x00, streamFile) == 0x5447566B) { /* "kVGT" */
+        offset = read_32bitLE(0x04, streamFile);
+    }
+
+    if (read_32bitBE(offset + 0x00, streamFile) != 0x31534E68 &&  /* "1SNh" */
+        read_32bitBE(offset + 0x00, streamFile) != 0x53454144)    /* "SEAD" */
         goto fail;
 
     /* stream is divided into blocks/chunks: 1SNh=audio header, 1SNd=data xN, 1SNl=loop end, 1SNe=end.
-     * Video uses various blocks (kVGT/fVGT/etc) and sometimes alt audio blocks (SEAD/SNDC/SEND). */
-    ea.is_sead = read_32bitBE(0x00,streamFile) == 0x53454144;
+     * Video uses various blocks (TGVk/TGVf/MUVf/etc) and sometimes alt audio blocks (SEAD/SNDC/SEND). */
+    ea.is_sead = read_32bitBE(offset + 0x00, streamFile) == 0x53454144;
 
-    /* use block size as endian marker (Saturn = BE) */
-    ea.big_endian = guess_endianness32bit(0x04,streamFile);
+    eacs_offset = offset + 0x08; /* after 1SNh block id+size */
 
-    eacs_offset = 0x08; /* after 1SNh block id+size */
-
-    if (!parse_header(streamFile,&ea, eacs_offset))
+    if (!parse_header(streamFile, &ea, eacs_offset))
         goto fail;
-    return init_vgmstream_main(streamFile, &ea);
+    vgmstream = init_vgmstream_main(streamFile, &ea);
+    if (!vgmstream) goto fail;
+
+    if (ea.num_samples == 0) {
+        /* header does not specify number of samples, need to calculate it manually */
+        /* HACK: we need vgmstream object to use blocked layout so we're doing this calc after creating it */
+        set_ea_1snh_num_samples(vgmstream, streamFile, &ea, 0);
+
+        /* update samples and loop state */
+        vgmstream->num_samples = ea.num_samples;
+        vgmstream_force_loop(vgmstream, ea.loop_flag, ea.loop_start, ea.loop_end);
+    }
+
+    return vgmstream;
 
 fail:
     return NULL;
@@ -113,6 +135,8 @@ VGMSTREAM * init_vgmstream_ea_eacs(STREAMFILE *streamFile) {
             if (ea.total_subsongs == target_subsong) {
                 /* 0x00: some id or flags? */
                 eacs_offset = read_32bitLE(bank_offset + 0x04, streamFile);
+                if (read_32bitBE(eacs_offset, streamFile) != 0x45414353)
+                    goto fail;
                 /* rest: not sure if part of this header */
             }
         }
@@ -161,13 +185,13 @@ static VGMSTREAM * init_vgmstream_main(STREAMFILE *streamFile, ea_header* ea) {
             vgmstream->coding_type = coding_ULAW_int;
             break;
 
-        case EA_CODEC_IMA: /* Need for Speed II (PC) */
+        case EA_CODEC_IMA: /* Need for Speed (PC) */
             if (ea->bits && ea->bits != 2) goto fail; /* only in EACS */
             vgmstream->coding_type = coding_DVI_IMA; /* stereo/mono, high nibble first */
             vgmstream->codec_config = ea->codec_config;
             break;
 
-        case EA_CODEC_PSX: /* Need for Speed (PS) */
+        case EA_CODEC_PSX: /* Need for Speed (PS1) */
             vgmstream->coding_type = coding_PSX;
             break;
 
@@ -176,9 +200,9 @@ static VGMSTREAM * init_vgmstream_main(STREAMFILE *streamFile, ea_header* ea) {
             goto fail;
     }
 
-
     if (!vgmstream_open_stream(vgmstream,streamFile,ea->data_offset))
         goto fail;
+
     return vgmstream;
 
 fail:
@@ -187,10 +211,14 @@ fail:
 }
 
 static int parse_header(STREAMFILE* streamFile, ea_header* ea, off_t offset) {
-    int32_t (*read_32bit)(off_t,STREAMFILE*) = ea->big_endian ? read_32bitBE : read_32bitLE;
+    /* audio header endianness doesn't always match block headers, use sample rate to detect */
+    int32_t (*read_32bit)(off_t,STREAMFILE*);
 
     if (read_32bitBE(offset+0x00, streamFile) == 0x45414353) { /* "EACS" */
         /* EACS subheader (PC, SAT) */
+        ea->big_endian = guess_endianness32bit(offset + 0x04, streamFile);
+        read_32bit = ea->big_endian ? read_32bitBE : read_32bitLE;
+
         ea->sample_rate = read_32bit(offset+0x04, streamFile);
         ea->bits        =  read_8bit(offset+0x08, streamFile);
         ea->channels    =  read_8bit(offset+0x09, streamFile);
@@ -208,28 +236,40 @@ static int parse_header(STREAMFILE* streamFile, ea_header* ea, off_t offset) {
             ea->codec_config = get_ea_1snh_ima_version(streamFile, 0x00, ea);
         /* EACS banks with empty values exist but will be rejected later */
     }
+    else if (read_32bitBE(offset + 0x00, streamFile) == 0x00) {
+        /* found in early videos, similar to EACS */
+        ea->big_endian = guess_endianness32bit(offset + 0x04, streamFile);
+        read_32bit = ea->big_endian ? read_32bitBE : read_32bitLE;
+
+        ea->sample_rate = read_32bit(offset + 0x04, streamFile);
+        ea->bits = read_8bit(offset + 0x08, streamFile);
+        ea->channels = read_8bit(offset + 0x09, streamFile);
+        ea->codec = read_8bit(offset + 0x0a, streamFile);
+        ea->type = read_8bit(offset + 0x0b, streamFile); /* block type? 0=1SNh, -1=bank */
+
+        if (ea->codec == EA_CODEC_IMA)
+            ea->codec_config = get_ea_1snh_ima_version(streamFile, 0x00, ea);
+    }
     else if (ea->is_sead) {
         /* alt subheader (found in some PC videos) */
+        ea->big_endian = guess_endianness32bit(offset + 0x00, streamFile);
+        read_32bit = ea->big_endian ? read_32bitBE : read_32bitLE;
+
         ea->sample_rate = read_32bit(offset+0x00, streamFile);
         ea->channels    = read_32bit(offset+0x04, streamFile);
         ea->codec       = read_32bit(offset+0x08, streamFile);
 
         if (ea->codec == EA_CODEC_IMA)
             ea->codec_config = get_ea_1snh_ima_version(streamFile, 0x00, ea);
-
-        set_ea_1snh_num_samples(streamFile, 0x00, ea);
-        if (ea->loop_start_offset) /* offset found, now find actual start sample */
-            set_ea_1snh_num_samples(streamFile, 0x00, ea);
     }
     else {
-        /* alt subheader (PS) */
+        /* alt subheader (PS1) */
+        ea->big_endian = guess_endianness32bit(offset + 0x00, streamFile);
+        read_32bit = ea->big_endian ? read_32bitBE : read_32bitLE;
+
         ea->sample_rate = read_32bit(offset+0x00, streamFile);
         ea->channels    =  read_8bit(offset+0x18, streamFile);
         ea->codec       = EA_CODEC_PSX;
-
-        set_ea_1snh_num_samples(streamFile, 0x00, ea);
-        if (ea->loop_start_offset) /* offset found, now find actual start sample */
-            set_ea_1snh_num_samples(streamFile, 0x00, ea);
     }
 
     ea->loop_flag = (ea->loop_end > 0);
@@ -238,69 +278,48 @@ static int parse_header(STREAMFILE* streamFile, ea_header* ea, off_t offset) {
 }
 
 /* get total samples by parsing block headers, needed when EACS isn't present */
-static void set_ea_1snh_num_samples(STREAMFILE* streamFile, off_t start_offset, ea_header* ea) {
-    int num_samples = 0, loop_start = 0, loop_end = 0, loop_start_offset = 0;
-    off_t block_offset = start_offset;
-    size_t block_size, block_header, block_samples;
-    int32_t (*read_32bit)(off_t,STREAMFILE*) = ea->big_endian ? read_32bitBE : read_32bitLE;
-    size_t file_size = get_streamfile_size(streamFile);
+static void set_ea_1snh_num_samples(VGMSTREAM *vgmstream, STREAMFILE *streamFile, ea_header *ea, int find_loop) {
+    int32_t num_samples = 0, block_id;
+    size_t file_size;
+    int32_t(*read_32bit)(off_t, STREAMFILE *) = ea->big_endian ? read_32bitBE : read_32bitLE;
+    int loop_end_found = 0;
 
+    file_size = get_streamfile_size(streamFile);
+    vgmstream->next_block_offset = ea->data_offset;
 
-    while (block_offset < file_size) {
-        uint32_t id = read_32bitBE(block_offset+0x00,streamFile);
-        block_size  =   read_32bit(block_offset+0x04,streamFile);
-        block_header = 0;
-        block_samples = 0;
-
-        if (id == 0x31534E68 || id == 0x53454144) {  /* "1SNh" "SEAD" audio header */
-            int is_sead = (id == 0x53454144);
-            int is_eacs = read_32bitBE(block_offset+0x08, streamFile) == 0x45414353;
-
-            block_header = is_eacs ? 0x28 : (is_sead ? 0x14 : 0x2c);
-            if (block_header >= block_size) /* sometimes has audio data after header */
-                block_header = 0;
-        }
-        else if (id == 0x31534E64 || id == 0x534E4443) {  /* "1SNd" "SNDC" audio data */
-            block_header = 0x08;
-        }
-        else if (id == 0x00000000 || id == 0xFFFFFFFF || id == 0x31534E65) { /* EOF or "1SNe" */
+    while (vgmstream->next_block_offset < file_size) {
+        block_update_ea_1snh(vgmstream->next_block_offset, vgmstream);
+        if (vgmstream->current_block_samples < 0)
             break;
-        }
-        else if (id == 0x31534E6C) {  /* "1SNl" loop point found */
-            loop_start_offset = read_32bit(block_offset+0x08,streamFile);
-            loop_end = num_samples;
-        }
 
-        if (block_header) {
-            switch(ea->codec) {
-                case EA_CODEC_PSX:
-                    block_samples = ps_bytes_to_samples(block_size - block_header, ea->channels);
-                    break;
-                case EA_CODEC_IMA:
-                    if (ea->codec_config == 1)
-                        block_samples = read_32bit(block_offset + block_header, streamFile);
-                    else
-                        block_samples = ima_bytes_to_samples(block_size - block_header, ea->channels);
-                    break;
+        block_id = read_32bitBE(vgmstream->current_block_offset, streamFile);
+        if (find_loop) {
+            if (vgmstream->current_block_offset == ea->loop_start_offset) {
+                ea->loop_start = num_samples;
+                ea->loop_flag = 1;
+                block_update_ea_1snh(ea->data_offset, vgmstream);
+                return;
+            }
+        } else {
+            if (block_id == 0x31534E6C) {  /* "1SNl" loop point found */
+                ea->loop_start_offset = read_32bit(vgmstream->current_block_offset + 0x08, streamFile);
+                ea->loop_end = num_samples;
+                loop_end_found = 1;
             }
         }
 
-
-        /* if there is a loop start offset set, this was called again just to find it */
-        if (ea->loop_start_offset && ea->loop_start_offset == block_offset) {
-            ea->loop_start = num_samples;
-            return;
-        }
-
-        num_samples += block_samples;
-        block_offset += block_size;
+        num_samples += vgmstream->current_block_samples;
     }
 
-
     ea->num_samples = num_samples;
-    ea->loop_start = loop_start;
-    ea->loop_end = loop_end;
-    ea->loop_start_offset = loop_start_offset;
+
+    /* reset once we're done */
+    block_update_ea_1snh(ea->data_offset, vgmstream);
+
+    if (loop_end_found) {
+        /* recurse to find loop start sample */
+        set_ea_1snh_num_samples(vgmstream, streamFile, ea, 1);
+    }
 }
 
 /* find codec version used, with or without ADPCM hist per block */
@@ -311,10 +330,13 @@ static int get_ea_1snh_ima_version(STREAMFILE* streamFile, off_t start_offset, c
 
     while (block_offset < file_size) {
         uint32_t id = read_32bitBE(block_offset+0x00,streamFile);
+        size_t block_size;
 
-        size_t block_size = read_32bitLE(block_offset+0x04,streamFile);
-        if (block_size > 0x00F00000) /* BE in SAT, but one file may have both BE and LE chunks */
-            block_size = read_32bitBE(block_offset+0x04,streamFile);
+        /* BE in SAT, but one file may have both BE and LE chunks */
+        if (guess_endianness32bit(block_offset + 0x04, streamFile))
+            block_size = read_32bitBE(block_offset + 0x04, streamFile);
+        else
+            block_size = read_32bitLE(block_offset + 0x04, streamFile);
 
         if (id == 0x31534E64 || id == 0x534E4443) {  /* "1SNd" "SNDC" audio data */
             size_t ima_samples = read_32bit(block_offset + 0x08, streamFile);
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c
index 09b4f5674..33e9c95b1 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c
@@ -855,8 +855,8 @@ typedef struct {
     off_t loop_offset;
 } eaac_header;
 
-static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *streamData, eaac_header *eaac);
-static layered_layout_data* build_layered_eaaudiocore_eaxma(STREAMFILE *streamFile, eaac_header *eaac);
+static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *sf_data, eaac_header *eaac);
+static layered_layout_data* build_layered_eaaudiocore(STREAMFILE *streamFile, eaac_header *eaac);
 static size_t calculate_eaac_size(VGMSTREAM *vgmstream, STREAMFILE *streamFile, eaac_header *eaac, off_t start_offset);
 
 /* EA newest header from RwAudioCore (RenderWare?) / EAAudioCore library (still generated by sx.exe).
@@ -992,7 +992,7 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST
                 vgmstream->layout_type = layout_segmented;
             }
             else {
-                vgmstream->layout_data = build_layered_eaaudiocore_eaxma(streamData, &eaac);
+                vgmstream->layout_data = build_layered_eaaudiocore(streamData, &eaac);
                 if (!vgmstream->layout_data) goto fail;
                 vgmstream->coding_type = coding_FFmpeg;
                 vgmstream->layout_type = layout_layered;
@@ -1105,21 +1105,10 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST
 
 #ifdef VGM_USE_FFMPEG
         case EAAC_CODEC_EAOPUS: { /* EAOpus (unknown FourCC) [FIFA 17 (PC), FIFA 19 (Switch)]*/
-            int skip = 0;
-            size_t data_size;
-
-            /* We'll remove EA blocks and pass raw data to FFmpeg Opus decoder */
-
-            temp_streamFile = setup_eaac_streamfile(streamData, eaac.version, eaac.codec, eaac.streamed,0,0, eaac.stream_offset);
-            if (!temp_streamFile) goto fail;
-
-            skip = ea_opus_get_encoder_delay(0x00, temp_streamFile);
-            data_size = get_streamfile_size(temp_streamFile);
-
-            vgmstream->codec_data = init_ffmpeg_ea_opus(temp_streamFile, 0x00,data_size, vgmstream->channels, skip, vgmstream->sample_rate);
-            if (!vgmstream->codec_data) goto fail;
+            vgmstream->layout_data = build_layered_eaaudiocore(streamData, &eaac);
+            if (!vgmstream->layout_data) goto fail;
             vgmstream->coding_type = coding_FFmpeg;
-            vgmstream->layout_type = layout_none;
+            vgmstream->layout_type = layout_layered;
             break;
         }
 #endif
@@ -1203,9 +1192,9 @@ static size_t calculate_eaac_size(VGMSTREAM *vgmstream, STREAMFILE *streamFile,
  * We use the segmented layout, since the eaac_streamfile doesn't handle padding,
  * and the segments seem fully separate (so even skipping would probably decode wrong). */
 // todo reorganize code for more standard init
-static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *streamData, eaac_header *eaac) {
+static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *sf_data, eaac_header *eaac) {
     segmented_layout_data *data = NULL;
-    STREAMFILE* temp_streamFile = NULL;
+    STREAMFILE* temp_sf = NULL;
     off_t offsets[2] = { eaac->stream_offset, eaac->loop_offset };
     off_t start_offset;
     int num_samples[2] = { eaac->loop_start, eaac->num_samples - eaac->loop_start};
@@ -1235,7 +1224,7 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st
             start_offset = 0x00; /* must point to the custom streamfile's beginning */
 
             /* layers inside segments, how trippy */
-            data->segments[i]->layout_data = build_layered_eaaudiocore_eaxma(streamData, &temp_eaac);
+            data->segments[i]->layout_data = build_layered_eaaudiocore(sf_data, &temp_eaac);
             if (!data->segments[i]->layout_data) goto fail;
             data->segments[i]->coding_type = coding_FFmpeg;
             data->segments[i]->layout_type = layout_layered;
@@ -1260,10 +1249,10 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st
 
                 start_offset = 0x00; /* must point to the custom streamfile's beginning */
 
-                temp_streamFile = setup_eaac_streamfile(streamData, eaac->version,eaac->codec,eaac->streamed,0,0, offsets[i]);
-                if (!temp_streamFile) goto fail;
+                temp_sf = setup_eaac_streamfile(sf_data, eaac->version,eaac->codec,eaac->streamed,0,0, offsets[i]);
+                if (!temp_sf) goto fail;
 
-                data->segments[i]->codec_data = init_mpeg_custom(temp_streamFile, 0x00, &data->segments[i]->coding_type, eaac->channels, type, &cfg);
+                data->segments[i]->codec_data = init_mpeg_custom(temp_sf, 0x00, &data->segments[i]->coding_type, eaac->channels, type, &cfg);
                 if (!data->segments[i]->codec_data) goto fail;
                 data->segments[i]->layout_type = layout_none;
                 break;
@@ -1273,14 +1262,14 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st
                 goto fail;
         }
 
-        if (!vgmstream_open_stream(data->segments[i],temp_streamFile == NULL ? streamData : temp_streamFile, start_offset))
+        if (!vgmstream_open_stream(data->segments[i],temp_sf == NULL ? sf_data : temp_sf, start_offset))
             goto fail;
 
-        close_streamfile(temp_streamFile);
-        temp_streamFile = NULL;
+        close_streamfile(temp_sf);
+        temp_sf = NULL;
 
         //todo temp_streamFile doesn't contain EAXMA's streamfile
-        data->segments[i]->stream_size = calculate_eaac_size(data->segments[i], temp_streamFile, eaac, start_offset);
+        data->segments[i]->stream_size = calculate_eaac_size(data->segments[i], temp_sf, eaac, start_offset);
     }
 
     if (!setup_layout_segmented(data))
@@ -1288,14 +1277,14 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st
     return data;
 
 fail:
-    close_streamfile(temp_streamFile);
+    close_streamfile(temp_sf);
     free_layout_segmented(data);
     return NULL;
 }
 
-static layered_layout_data* build_layered_eaaudiocore_eaxma(STREAMFILE *streamData, eaac_header *eaac) {
+static layered_layout_data* build_layered_eaaudiocore(STREAMFILE *sf_data, eaac_header *eaac) {
     layered_layout_data* data = NULL;
-    STREAMFILE* temp_streamFile = NULL;
+    STREAMFILE* temp_sf = NULL;
     int i, layers = (eaac->channels+1) / 2;
 
 
@@ -1303,9 +1292,7 @@ static layered_layout_data* build_layered_eaaudiocore_eaxma(STREAMFILE *streamDa
     data = init_layout_layered(layers);
     if (!data) goto fail;
 
-    /* open each layer subfile (1/2ch streams: 2ch+2ch..+1ch or 2ch+2ch..+2ch).
-     * EA-XMA uses completely separate 1/2ch streams, unlike standard XMA that interleaves 1/2ch streams
-     * with a skip counter to reinterleave (so EA-XMA streams don't have skips set) */
+    /* open each layer subfile (1/2ch streams: 2ch+2ch..+1ch or 2ch+2ch..+2ch). */
     for (i = 0; i < layers; i++) {
         int layer_channels = (i+1 == layers && eaac->channels % 2 == 1) ? 1 : 2; /* last layer can be 1/2ch */
 
@@ -1319,44 +1306,71 @@ static layered_layout_data* build_layered_eaaudiocore_eaxma(STREAMFILE *streamDa
         data->layers[i]->loop_end_sample = eaac->loop_end;
 
 #ifdef VGM_USE_FFMPEG
-        {
-            uint8_t buf[0x100];
-            int bytes, block_size, block_count;
-            size_t stream_size;
+        switch(eaac->codec) {
+            /* EA-XMA uses completely separate 1/2ch streams, unlike standard XMA that interleaves 1/2ch
+             * streams with a skip counter to reinterleave (so EA-XMA streams don't have skips set) */
+            case EAAC_CODEC_EAXMA: {
+                uint8_t buf[0x100];
+                int bytes, block_size, block_count;
+                size_t stream_size;
 
-            temp_streamFile = setup_eaac_streamfile(streamData, eaac->version, eaac->codec, eaac->streamed,i,layers, eaac->stream_offset);
-            if (!temp_streamFile) goto fail;
+                temp_sf = setup_eaac_streamfile(sf_data, eaac->version, eaac->codec, eaac->streamed,i,layers, eaac->stream_offset);
+                if (!temp_sf) goto fail;
 
-            stream_size = get_streamfile_size(temp_streamFile);
-            block_size = 0x10000; /* unused */
-            block_count = stream_size / block_size + (stream_size % block_size ? 1 : 0);
+                stream_size = get_streamfile_size(temp_sf);
+                block_size = 0x10000; /* unused */
+                block_count = stream_size / block_size + (stream_size % block_size ? 1 : 0);
 
-            bytes = ffmpeg_make_riff_xma2(buf, 0x100, data->layers[i]->num_samples, stream_size, data->layers[i]->channels, data->layers[i]->sample_rate, block_count, block_size);
-            data->layers[i]->codec_data = init_ffmpeg_header_offset(temp_streamFile, buf,bytes, 0x00, stream_size);
-            if (!data->layers[i]->codec_data) goto fail;
+                bytes = ffmpeg_make_riff_xma2(buf, 0x100, data->layers[i]->num_samples, stream_size, data->layers[i]->channels, data->layers[i]->sample_rate, block_count, block_size);
+                data->layers[i]->codec_data = init_ffmpeg_header_offset(temp_sf, buf,bytes, 0x00, stream_size);
+                if (!data->layers[i]->codec_data) goto fail;
 
-            data->layers[i]->coding_type = coding_FFmpeg;
-            data->layers[i]->layout_type = layout_none;
-            data->layers[i]->stream_size = get_streamfile_size(temp_streamFile);
+                data->layers[i]->coding_type = coding_FFmpeg;
+                data->layers[i]->layout_type = layout_none;
+                data->layers[i]->stream_size = get_streamfile_size(temp_sf);
+
+                xma_fix_raw_samples(data->layers[i], temp_sf, 0x00,stream_size, 0, 0,0); /* samples are ok? */
+                break;
+            }
+
+            /* Opus can do multichannel just fine, but that wasn't weird enough for EA */
+            case EAAC_CODEC_EAOPUS: {
+                int skip;
+                size_t data_size;
+
+                /* We'll remove EA blocks and pass raw data to FFmpeg Opus decoder */
+                temp_sf = setup_eaac_streamfile(sf_data, eaac->version, eaac->codec, eaac->streamed,i,layers, eaac->stream_offset);
+                if (!temp_sf) goto fail;
+
+                skip = ea_opus_get_encoder_delay(0x00, temp_sf);
+                data_size = get_streamfile_size(temp_sf);
+
+                data->layers[i]->codec_data = init_ffmpeg_ea_opus(temp_sf, 0x00,data_size, layer_channels, skip, eaac->sample_rate);
+                if (!data->layers[i]->codec_data) goto fail;
+                data->layers[i]->coding_type = coding_FFmpeg;
+                data->layers[i]->layout_type = layout_none;
+
+                //TODO: 6ch channel layout seems L C R BL BR LFE, not sure about other EAAC
+                break;
+            }
 
-            xma_fix_raw_samples(data->layers[i], temp_streamFile, 0x00,stream_size, 0, 0,0); /* samples are ok? */
         }
 #else
         goto fail;
 #endif
 
-        if ( !vgmstream_open_stream(data->layers[i], temp_streamFile, 0x00) ) {
+        if ( !vgmstream_open_stream(data->layers[i], temp_sf, 0x00) ) {
             goto fail;
         }
     }
 
     if (!setup_layout_layered(data))
         goto fail;
-    close_streamfile(temp_streamFile);
+    close_streamfile(temp_sf);
     return data;
 
 fail:
-    close_streamfile(temp_streamFile);
+    close_streamfile(temp_sf);
     free_layout_layered(data);
     return NULL;
 }
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_opus_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_opus_streamfile.h
new file mode 100644
index 000000000..a71b810e4
--- /dev/null
+++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_opus_streamfile.h
@@ -0,0 +1,242 @@
+#ifndef _EA_EAAC_OPUS_STREAMFILE_H_
+#define _EA_EAAC_OPUS_STREAMFILE_H_
+#include "../streamfile.h"
+
+typedef struct deblock_config_t deblock_config_t;
+typedef struct deblock_io_data deblock_io_data;
+
+ struct deblock_config_t {
+    /* config (all optional) */
+    size_t logical_size;    /* pre-calculated size for performance (otherwise has to read the whole thing) */
+    off_t stream_start;     /* data start */
+    size_t stream_size;     /* data max */
+
+    size_t chunk_size;      /* some size like a constant interleave */
+    size_t skip_size;       /* same */
+
+    int codec;              /* codec or type variations */
+    int channels;
+    int big_endian;
+    uint32_t config;        /* some non-standard config value */
+
+    /* read=blocks from out stream to read) and "steps" (blocks from other streams to skip) */
+    int step_start;         /* initial step_count at stream start (often 0) */
+    int step_count;         /* number of blocks to step over from other streams */
+    int read_count;         /* number of blocks to read from this stream, after steps */
+
+    size_t track_size;
+    int track_number;
+    int track_count;
+    size_t interleave_count;
+    size_t interleave_last_count;
+
+    /* callback that setups deblock_io_data state, normally block_size and data_size */
+    void (*block_callback)(STREAMFILE *sf, off_t offset, deblock_io_data *data);
+} ;
+
+
+struct  deblock_io_data{
+    /* initial config */
+    deblock_config_t cfg;
+
+    /* state */
+    off_t logical_offset;   /* fake deblocked offset */
+    off_t physical_offset;  /* actual file offset */
+    off_t block_size;       /* current block (added to physical offset) */
+    off_t skip_size;        /* data to skip from block start to reach data (like a header) */
+    off_t data_size;        /* usable data in a block (added to logical offset) */
+//todo head/foot?
+    int step_count;         /* number of blocks to step over */
+    int read_count;         /* number of blocks to read */
+
+    size_t logical_size;
+    size_t physical_size;
+    off_t physical_end;
+} ;
+
+
+static void block_callback_default(STREAMFILE *sf, off_t offset, deblock_io_data *data) {
+    data->block_size = data->cfg.chunk_size;
+    data->skip_size = data->cfg.skip_size;
+    data->data_size = data->block_size - data->skip_size;
+}
+
+static size_t deblock_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, deblock_io_data* data) {
+    size_t total_read = 0;
+
+
+    /* re-start when previous offset (can't map logical<>physical offsets) */
+    if (data->logical_offset < 0 || offset < data->logical_offset) {
+        ;VGM_LOG("DEBLOCK: restart offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset);
+        data->physical_offset = data->cfg.stream_start;
+        data->logical_offset = 0x00;
+        data->block_size = 0;
+        data->data_size = 0;
+        data->skip_size = 0;
+
+        data->step_count = data->cfg.step_start;
+        data->read_count = data->cfg.read_count;
+    }
+
+    /* read blocks */
+    while (length > 0) {
+
+        /* ignore EOF */
+        if (offset < 0 ||
+                (data->physical_offset >= data->cfg.stream_start + data->physical_size) ||
+                (data->logical_size > 0 && offset > data->logical_size)) {
+            break;
+        }
+
+        /* process new block */
+        if (data->data_size <= 0) {
+            data->cfg.block_callback(sf, offset, data);
+
+            if (data->block_size <= 0) {
+                VGM_LOG("DEBLOCK: block size not set at %lx\n", data->physical_offset);
+                break;
+            }
+        }
+
+#if 1
+        if (data->step_count > 0) {
+            data->step_count--;
+            data->physical_offset += data->block_size;
+            data->data_size = 0;
+            continue;
+        }
+#else
+        /* handle blocks from multiple streams */
+        {
+            if (data->step_count > 0) {
+                data->step_count--;
+                data->data_size = 0; /* step over this block */
+            }
+            else if (data->read_count) {//must detect when blocks has been read
+                data->read_count--; /* read this block */
+
+                /* reset */
+                if (data->step_count == 0 && data->read_count == 0) {
+                    data->step_count = data->cfg.step_count;
+                    data->read_count = data->cfg.read_count;
+                }
+            }
+        }
+#endif
+        /* move to next block */
+        if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
+            data->physical_offset += data->block_size;
+            data->logical_offset += data->data_size;
+            data->data_size = 0;
+
+            data->step_count = data->cfg.step_count;
+            //VGM_LOG("ignore at %lx + %lx, skips=%i, reads=%i\n", data->physical_offset, data->block_size, data->step_count, data->read_count);
+            continue;
+        }
+
+        //VGM_LOG("accept at %lx + %lx, skips=%i, reads=%i\n", data->physical_offset, data->block_size, data->step_count, data->read_count);
+
+        /* read block 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, sf);
+
+            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 deblock_io_size(STREAMFILE *streamfile, deblock_io_data* data) {
+    uint8_t buf[0x04];
+
+    if (data->logical_size)
+        return data->logical_size;
+
+    if (data->cfg.logical_size) {
+        data->logical_size = data->cfg.logical_size;
+        return data->logical_size;
+    }
+
+    /* force a fake read at max offset, to get max logical_offset (will be reset next read) */
+    deblock_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
+    data->logical_size = data->logical_offset;
+    return data->logical_size;
+}
+
+/* generic "de-blocker" helper for streams divided in blocks that have weird interleaves, their
+ * decoder can't easily use blocked layout, or some other weird feature. Must pass a
+ * deblock_config_t with setup and a callback that sets sizes of a single block. */
+static STREAMFILE* open_io_deblocker_streamfile_f(STREAMFILE *sf, deblock_config_t *cfg) {
+    STREAMFILE *new_sf = NULL;
+    deblock_io_data io_data = {0};
+
+    /* prepare data */
+    io_data.cfg = *cfg; /* memcpy */
+
+    if (io_data.cfg.block_callback == NULL)
+        io_data.cfg.block_callback = block_callback_default;
+
+    if (io_data.cfg.stream_start < 0)
+        goto fail;
+    if (io_data.cfg.step_start < 0 || io_data.cfg.step_count < 0)
+        goto fail;
+
+    if (io_data.cfg.read_count == 0)
+        io_data.cfg.read_count = 1;
+
+    io_data.physical_size = io_data.cfg.stream_size;
+    if (io_data.physical_size > get_streamfile_size(sf) + io_data.cfg.stream_start || io_data.physical_size == 0)
+        io_data.physical_size = get_streamfile_size(sf) - io_data.cfg.stream_start;
+    io_data.physical_end = io_data.cfg.stream_start + io_data.physical_size;
+VGM_LOG("ps=%x, pe=%lx\n", io_data.physical_size, io_data.physical_end);
+    io_data.logical_offset = -1; /* read reset */
+
+    //TODO: other validations
+
+    /* setup subfile */
+    new_sf = open_io_streamfile_f(sf, &io_data, sizeof(deblock_io_data), deblock_io_read, deblock_io_size);
+    return new_sf;
+fail:
+    VGM_LOG("DEBLOCK: bad init\n");
+    close_streamfile(sf);
+    return NULL;
+}
+
+/*****************************************************/
+
+static void block_callback(STREAMFILE *sf, off_t offset, deblock_io_data *data) {
+    /* read the whole block, will be skipped for unwanted sub-streams */
+    data->block_size = 0x02 + read_u16be(data->physical_offset, sf);
+    data->data_size = data->block_size;
+    //VGM_LOG("read at %lx + %lx, skips=%i, reads=%i\n", data->physical_offset, data->block_size, data->step_count, data->read_count);
+}
+
+static STREAMFILE* open_io_eaac_opus_streamfile_f(STREAMFILE *new_sf, int stream_number, int stream_count) {
+    deblock_config_t cfg = {0};
+
+    cfg.step_start = stream_number;
+    cfg.step_count = stream_count - 1;
+    cfg.block_callback = block_callback;
+    /* starts from 0 since new_sf is pre-deblocked */
+
+    /* setup subfile */
+    //new_sf = open_wrap_streamfile(sf); /* to be used with others */
+    new_sf = open_io_deblocker_streamfile_f(new_sf, &cfg);
+    return new_sf;
+}
+
+#endif /* _EA_EAAC_OPUS_STREAMFILE_H_ */
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_streamfile.h
index e74d1ee1c..160517f50 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_streamfile.h
+++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_streamfile.h
@@ -1,6 +1,7 @@
 #ifndef _EA_EAAC_STREAMFILE_H_
 #define _EA_EAAC_STREAMFILE_H_
 #include "../streamfile.h"
+#include "ea_eaac_opus_streamfile.h"
 
 #define XMA_FRAME_SIZE 0x800
 
@@ -40,7 +41,7 @@ static size_t eaac_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset,
     /* previous offset: re-start as we can't map logical<>physical offsets
      * (kinda slow as it trashes buffers, but shouldn't happen often) */
     if (offset < data->logical_offset) {
-        //;VGM_LOG("IO restart: offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset);
+        ;VGM_LOG("EAAC IO: restart offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset);
         data->physical_offset = data->stream_offset;
         data->logical_offset = 0x00;
         data->data_size = 0;
@@ -260,8 +261,9 @@ static STREAMFILE* setup_eaac_streamfile(STREAMFILE *sf, int version, int codec,
     /* setup subfile */
     new_sf = open_wrap_streamfile(sf);
     new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(eaac_io_data), eaac_io_read, eaac_io_size);
-    if (codec == 0x03) /* EA-XMA only since logical data is bigger */
-        new_sf = open_buffer_streamfile_f(new_sf, 0);
+    new_sf = open_buffer_streamfile_f(new_sf, 0); /* EA-XMA and multichannel EALayer3 benefit from this */
+    if (codec == 0x0c && stream_count > 1) /* multichannel opus */
+        new_sf = open_io_eaac_opus_streamfile_f(new_sf, stream_number, stream_count);
     return new_sf;
 }
 
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c
index 792a0d68f..d54c54e2b 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c
@@ -173,13 +173,25 @@ VGMSTREAM * init_vgmstream_ea_schl_video(STREAMFILE *streamFile) {
 
 
     /* check extension */
-    /* .vp6: ~late */
-    if (!check_extensions(streamFile,"vp6"))
-        goto fail;
-
-    /* check initial movie block id */
-    if (read_32bitBE(0x00,streamFile) != 0x4D566864) /* "MVhd" */
+    /* .uv: early */
+    /* .dct: early-mid [ex. Need for Speed II SE (PC), FIFA 98 (PC)] */
+    /* .mad: mid */
+    /* .vp6: late */
+    if (check_extensions(streamFile, "vp6")) {
+        /* check initial movie block id */
+        if (read_32bitBE(0x00, streamFile) != 0x4D566864) /* "MVhd" */
+            goto fail;
+    } else if (check_extensions(streamFile, "uv,dct")) {
+        /* starts with audio header block */
+        if (read_32bitBE(0x00, streamFile) != EA_BLOCKID_HEADER) /* "SCHl" */
+            goto fail;
+    } else if (check_extensions(streamFile, "mad")) {
+        /* check initial movie block id */
+        if (read_32bitBE(0x00, streamFile) != 0x4D41446B) /* "MADk" */
+            goto fail;
+    } else {
         goto fail;
+    }
 
     /* use block size to check endianness */
     if (guess_endianness32bit(0x04, streamFile)) {
@@ -208,7 +220,7 @@ VGMSTREAM * init_vgmstream_ea_schl_video(STREAMFILE *streamFile) {
         offset += block_size;
     }
 
-    if (start_offset == 0)
+    if (offset >= get_streamfile_size(streamFile))
         goto fail;
 
     /* find target subsong (one per each SHxx multilang block) */
@@ -469,6 +481,10 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE *streamFile) {
     STREAMFILE *datFile = NULL;
     VGMSTREAM *vgmstream;
 
+    /* checks */
+    if (!check_extensions(streamFile, "hdr"))
+        goto fail;
+
     /* main header is machine endian but it's not important here */
     /* 0x00: ID */
     /* 0x02: sub-ID (used for different police voices in NFS games) */
@@ -540,6 +556,10 @@ VGMSTREAM * init_vgmstream_ea_hdr_dat_v2(STREAMFILE *streamFile) {
     STREAMFILE *datFile = NULL;
     VGMSTREAM *vgmstream;
 
+    /* checks */
+    if (!check_extensions(streamFile, "hdr"))
+        goto fail;
+
     /* main header is machine endian but it's not important here */
     /* 0x00: ID */
     /* 0x02: userdata size */
@@ -603,12 +623,22 @@ fail:
 
 
 /* open map/mpf+mus pairs that aren't exact pairs, since EA's games can load any combo */
-static STREAMFILE * open_mapfile_pair(STREAMFILE *streamFile) {
+static STREAMFILE* open_mapfile_pair(STREAMFILE *streamFile) {
     static const char *const mapfile_pairs[][2] = {
         /* standard cases, replace map part with mus part (from the end to preserve prefixes) */
         {"MUS_CTRL.MPF","MUS_STR.MUS"}, /* GoldenEye - Rogue Agent (PS2) */
         {"mus_ctrl.mpf","mus_str.mus"}, /* GoldenEye - Rogue Agent (others) */
         {".mpf","_main.mus"}, /* 007 - Everything or Nothing (GC) */
+        {"AKA_Mus.mpf","Track.mus"}, /* Boogie (PS2) */
+        //TODO: improve pairs (needs better wildcard support)
+        //NSF2:
+        /* ZTRxxROK.MAP > ZTRxx.TRJ */
+        /* ZTRxxTEC.MAP > ZTRxx.TRM */
+        /* ZZSHOW.MAP and ZZSHOW2.MAP > ZZSHOW.MUS */
+        //NSF3:
+        /* ZTRxxROK.MAP > ZZZTRxxA.TRJ */
+        /* ZTRxxTEC.MAP > ZZZTRxxB.TRM */
+        /* other extra files that may need the hack below */
         /* hack when when multiple maps point to the same mus, uses name before "+"
          * ex. ZZZTR00A.TRJ+ZTR00PGR.MAP or ZZZTR00A.TRJ+ZTR00R0A.MAP both point to ZZZTR00A.TRJ */
         {"+",""}, /* Need for Speed III (PS1) */
@@ -1645,7 +1675,7 @@ static void update_ea_stream_size_and_samples(STREAMFILE* streamFile, off_t star
     }
 
     /* manually read totals */
-    block_update(start_offset, vgmstream);
+    vgmstream->next_block_offset = start_offset;
     while (vgmstream->next_block_offset < file_size) {
         block_update_ea_schl(vgmstream->next_block_offset, vgmstream);
         if (vgmstream->current_block_samples < 0)
@@ -1673,7 +1703,7 @@ static void update_ea_stream_size_and_samples(STREAMFILE* streamFile, off_t star
     }
 
     /* reset once we're done */
-    block_update(start_offset, vgmstream);
+    block_update_ea_schl(start_offset, vgmstream);
     
     /* only use calculated samples with multiple subfiles (rarely header samples may be less due to padding) */
     if (standalone && multiple_schl) {
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c b/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c
index f8363b0bd..1c6a356ff 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c
@@ -25,7 +25,7 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start,
     //    goto fail;
 
     /* don't try to open headers and other mini files */
-    if (get_streamfile_size(streamFile) <= 0x100)
+    if (get_streamfile_size(streamFile) <= 0x1000)
         goto fail;
 
 
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h
index ba97274f4..420594c94 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h
+++ b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h
@@ -300,6 +300,9 @@ static const hcakey_info hcakey_list[] = {
         /* Love Live! School idol festival ALL STARS (Android) */
         {6498535309877346413},      // 5A2F6F6F0192806D
 
+        /* BLACKSTAR -Theater Starless- (Android) */
+        {121837007188},             // 0000001C5E0D3154
+
         /* Dragalia Lost (Cygames) [iOS/Android] */
         {2967411924141,         subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) },    // 000002B2E7889CAD
 
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/isb.c b/Frameworks/vgmstream/vgmstream/src/meta/isb.c
new file mode 100644
index 000000000..bef078c4e
--- /dev/null
+++ b/Frameworks/vgmstream/vgmstream/src/meta/isb.c
@@ -0,0 +1,191 @@
+#include "meta.h"
+#include "../coding/coding.h"
+
+
+/* .ISB - Creative ISACT (Interactive Spatial Audio Composition Tools) middleware [Psychonauts (PC)] */
+VGMSTREAM * init_vgmstream_isb(STREAMFILE *streamFile) {
+    VGMSTREAM * vgmstream = NULL;
+    off_t start_offset = 0, name_offset = 0;
+    size_t stream_size = 0, name_size = 0;
+    int loop_flag = 0, channel_count = 0, sample_rate = 0, codec = 0, pcm_bytes = 0, bps = 0;
+    int total_subsongs, target_subsong = streamFile->stream_index;
+
+
+    /* checks */
+    if (!check_extensions(streamFile, "isb"))
+        goto fail;
+
+    if (read_32bitBE(0x00,streamFile) != 0x52494646) /* "RIFF" */
+        goto fail;
+    if (read_32bitLE(0x04,streamFile) + 0x08 != get_streamfile_size(streamFile))
+        goto fail;
+    if (read_32bitBE(0x08,streamFile) != 0x69736266) /* "isbf" */
+        goto fail;
+
+    /* some files have a companion .icb, seems to be a cue file pointing here */
+
+    /* format is RIFF with many custom chunks, apparently for their DAW-like editor with
+     * complex functions, but most seem always included by default and unused, and games
+     * Psychonauts seems to use the format as a simple audio bank. Mass Effect (X360)
+     * apparently uses ISACT, while Psychonauts Xbox/PS2 don't. */
+
+    {
+        off_t offset, max_offset, header_offset = 0;
+        size_t header_size = 0;
+
+        total_subsongs = 0; /* not specified */
+        if (target_subsong == 0) target_subsong = 1;
+
+        /* parse base RIFF */
+        offset = 0x0c;
+        max_offset = get_streamfile_size(streamFile);
+        while (offset < max_offset) {
+            uint32_t chunk_type = read_u32be(offset + 0x00,streamFile);
+            uint32_t chunk_size = read_s32le(offset + 0x04,streamFile);
+            offset += 0x08;
+
+            switch(chunk_type) {
+                case 0x4C495354: /* "LIST" */
+                    if (read_u32be(offset, streamFile) != 0x73616D70) /* "samp" */
+                        break; /* there are "bfob" LIST without data */
+
+                    total_subsongs++;
+                    if (target_subsong == total_subsongs && header_offset == 0) {
+                        header_offset = offset;
+                        header_size = chunk_size;
+                    }
+                    break;
+
+                default: /* most are common chunks at the start that seem to contain defaults */
+                    break;
+            }
+
+            //if (offset + chunk_size+0x01 <= max_offset && chunk_size % 0x02)
+            //    chunk_size += 0x01;
+            offset += chunk_size;
+        }
+
+        if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
+        if (header_offset == 0) goto fail;
+
+        /* parse header inside LIST */
+        offset = header_offset + 0x04;
+        max_offset = offset + header_size;
+        while (offset < max_offset) {
+            uint32_t chunk_type = read_u32be(offset + 0x00,streamFile);
+            uint32_t chunk_size = read_s32le(offset + 0x04,streamFile);
+            offset += 0x08;
+
+            switch(chunk_type) {
+                case 0x7469746C: /* "titl" */
+                    name_offset = offset;
+                    name_size = chunk_size;
+                    break;
+
+                case 0x63686E6B: /* "chnk" */
+                    channel_count = read_u32le(offset + 0x00, streamFile);
+                    break;
+
+                case 0x73696E66: /* "sinf" */
+                    /* 0x00: null? */
+                    /* 0x04: some value? */
+                    sample_rate = read_u32le(offset + 0x08, streamFile);
+                    pcm_bytes = read_u32le(offset + 0x0c, streamFile);
+                    bps = read_u16le(offset + 0x10, streamFile);
+                    /* 0x12: some value? */
+                    break;
+
+                case 0x636D7069: /* "cmpi" */
+                    codec = read_u32le(offset + 0x00, streamFile);
+                    if (read_u32le(offset + 0x04, streamFile) != codec) {
+                        VGM_LOG("ISB: unknown compression repeat\n");
+                        goto fail;
+                    }
+                    /* 0x08: extra value for some codecs? */
+                    /* 0x0c: block size when codec is XBOX-IMA */
+                    /* 0x10: null? */
+                    /* 0x14: flags? */
+                    break;
+
+                case 0x64617461: /* "data" */
+                    start_offset = offset;
+                    stream_size = chunk_size;
+                    break;
+
+                default: /* most of the same default chunks */
+                    break;
+            }
+
+            //if (offset + chunk_size+0x01 <= max_offset && chunk_size % 0x02)
+            //    chunk_size += 0x01;
+            offset += chunk_size;
+        }
+
+        if (start_offset == 0)
+            goto fail;
+    }
+
+
+    /* some files are marked */
+    loop_flag  = 0;
+
+
+    /* build the VGMSTREAM */
+    vgmstream = allocate_vgmstream(channel_count, loop_flag);
+    if (!vgmstream) goto fail;
+
+    vgmstream->meta_type = meta_WAF; //todo
+    vgmstream->sample_rate = sample_rate;
+    vgmstream->num_streams = total_subsongs;
+    vgmstream->stream_size = stream_size;
+
+    switch(codec) {
+        case 0x00:
+            if (bps == 8) {
+                vgmstream->coding_type = coding_PCM8_U;
+                vgmstream->layout_type = layout_interleave;
+                vgmstream->interleave_block_size = 0x01;
+            }
+            else {
+                vgmstream->coding_type = coding_PCM16LE;
+                vgmstream->layout_type = layout_interleave;
+                vgmstream->interleave_block_size = 0x02;
+            }
+            vgmstream->num_samples = pcm_bytes_to_samples(stream_size, channel_count, bps); /* unsure about pcm_bytes */
+            break;
+
+        case 0x01:
+            vgmstream->coding_type = coding_XBOX_IMA;
+            vgmstream->layout_type = layout_none;
+            vgmstream->num_samples = xbox_ima_bytes_to_samples(stream_size, channel_count); /* pcm_bytes has excess data */
+            break;
+
+#ifdef VGM_USE_VORBIS
+        case 0x02:
+            vgmstream->codec_data = init_ogg_vorbis(streamFile, start_offset, stream_size, NULL);
+            if (!vgmstream->codec_data) goto fail;
+            vgmstream->coding_type = coding_OGG_VORBIS;
+            vgmstream->layout_type = layout_none;
+            vgmstream->num_samples = pcm_bytes / channel_count / (bps/8);
+            break;
+#endif
+
+        default: /* according to press releases ISACT may support WMA and XMA */
+            VGM_LOG("ISB: unknown codec %i\n", codec);
+            goto fail;
+    }
+
+    if (name_offset) { /* UTF16 but only uses lower bytes */
+        if (name_size > STREAM_NAME_SIZE)
+            name_size = STREAM_NAME_SIZE;
+        read_string_utf16le(vgmstream->stream_name,name_size, name_offset, streamFile);
+    }
+
+    if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
+        goto fail;
+    return vgmstream;
+
+fail:
+    close_vgmstream(vgmstream);
+    return NULL;
+}
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/lsf.c b/Frameworks/vgmstream/vgmstream/src/meta/lsf.c
index 0f7ef2e32..94f7741d9 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/lsf.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/lsf.c
@@ -1,59 +1,47 @@
 #include "meta.h"
 #include "../util.h"
 
-/* .lsf - Fastlane Street Racing (iPhone) */
-/* "!n1nj4n" */
-
+/* .lsf - from Atod games [Fastlane Street Racing (iPhone), Chicane Street Racing prototype (Gizmondo)] */
 VGMSTREAM * init_vgmstream_lsf_n1nj4n(STREAMFILE *streamFile) {
     VGMSTREAM * vgmstream = NULL;
-    char filename[PATH_LIMIT];
-
-    size_t file_size;
     off_t start_offset;
+    int loop_flag, channel_count;
+    size_t file_size;
 
-    /* check extension, case insensitive */
-    streamFile->get_name(streamFile,filename,sizeof(filename));
-    if (strcasecmp("lsf",filename_extension(filename))) goto fail;
 
-    /* check header */
-    if (read_32bitBE(0x0, streamFile) != 0x216E316E || // "!n1n"
-        read_32bitBE(0x4, streamFile) != 0x6A346E00)   // "j4n\0"
+    /* checks */
+    if (!check_extensions(streamFile, "lsf"))
+        goto fail;
+
+    if (read_32bitBE(0x00, streamFile) != 0x216E316E || // "!n1n"
+        read_32bitBE(0x04, streamFile) != 0x6A346E00)   // "j4n\0"
         goto fail;
 
-    /* check size */
     file_size = get_streamfile_size(streamFile);
-    if (read_32bitLE(0xC, streamFile) + 0x10 != file_size)
+    if (read_32bitLE(0x0C, streamFile) + 0x10 != file_size)
         goto fail;
 
+    loop_flag = 0;
+    channel_count = 1;
     start_offset = 0x10;
 
+
     /* build the VGMSTREAM */
-    vgmstream = allocate_vgmstream(1,0);
+    vgmstream = allocate_vgmstream(channel_count, loop_flag);
     if (!vgmstream) goto fail;
 
-    /* fill in the vital statistics */
-    vgmstream->num_samples = (file_size-0x10)/0x1c*0x1b*2;
-    vgmstream->sample_rate = read_32bitLE(0x8, streamFile);
-
-    vgmstream->coding_type = coding_LSF;
-    vgmstream->layout_type = layout_none;
     vgmstream->meta_type = meta_LSF_N1NJ4N;
+    vgmstream->sample_rate = read_32bitLE(0x08, streamFile);
+    vgmstream->num_samples = (file_size-0x10)/0x1c*0x1b*2;
+    vgmstream->coding_type = coding_LSF;
+    vgmstream->layout_type = layout_interleave;
+    vgmstream->interleave_block_size = 0x1c;
 
-    /* open the file for reading */
-    {
-        vgmstream->ch[0].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
-
-        if (!vgmstream->ch[0].streamfile) goto fail;
-
-        vgmstream->ch[0].channel_start_offset=
-            vgmstream->ch[0].offset=start_offset;
-    }
-
+    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;
 }
-
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/meta.h b/Frameworks/vgmstream/vgmstream/src/meta/meta.h
index 87b24c54b..81aae0fe3 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/meta.h
+++ b/Frameworks/vgmstream/vgmstream/src/meta/meta.h
@@ -87,6 +87,7 @@ VGMSTREAM * init_vgmstream_ps2_mic(STREAMFILE *streamFile);
 VGMSTREAM * init_vgmstream_raw_pcm(STREAMFILE *streamFile);
 
 VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile);
+VGMSTREAM * init_vgmstream_vag_aaap(STREAMFILE *streamFile);
 
 VGMSTREAM * init_vgmstream_seb(STREAMFILE *streamFile);
 
@@ -427,8 +428,6 @@ VGMSTREAM * init_vgmstream_pona_psx(STREAMFILE* streamFile);
 
 VGMSTREAM * init_vgmstream_xbox_hlwav(STREAMFILE* streamFile);
 
-VGMSTREAM * init_vgmstream_stx(STREAMFILE* streamFile);
-
 VGMSTREAM * init_vgmstream_myspd(STREAMFILE* streamFile);
 
 VGMSTREAM * init_vgmstream_his(STREAMFILE* streamFile);
@@ -869,4 +868,8 @@ VGMSTREAM * init_vgmstream_ubi_hx(STREAMFILE * streamFile);
 
 VGMSTREAM * init_vgmstream_bmp_konami(STREAMFILE * streamFile);
 
+VGMSTREAM * init_vgmstream_isb(STREAMFILE * streamFile);
+
+VGMSTREAM* init_vgmstream_xssb(STREAMFILE *sf);
+
 #endif /*_META_H*/
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mta2.c b/Frameworks/vgmstream/vgmstream/src/meta/mta2.c
index 48023d375..a1ca1f3cd 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/mta2.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/mta2.c
@@ -8,7 +8,6 @@ VGMSTREAM * init_vgmstream_mta2(STREAMFILE *streamFile) {
     off_t start_offset;
     int loop_flag, channel_count, sample_rate;
     int32_t loop_start, loop_end;
-    uint32_t sample_rate_int;
 
 
     /* checks */
@@ -47,13 +46,9 @@ VGMSTREAM * init_vgmstream_mta2(STREAMFILE *streamFile) {
     }
 #endif
 
-    sample_rate_int = read_32bitBE(0x7c, streamFile);
-    if (sample_rate_int) { /* sample rate in 32b float (WHY?) typically 48000.0 */
-        float* sample_float = (float*)&sample_rate_int;
-        sample_rate = (int)*sample_float;
-    } else { /* default when not specified (most of the time) */
-        sample_rate = 48000;
-    }
+    sample_rate = (int)read_f32be(0x7c, streamFile); /* sample rate in 32b float (WHY?) typically 48000.0 */
+    if (sample_rate == 0)
+        sample_rate = 48000; /* default when not specified (most of the time) */
 
 
     /* TRKP chunks (x16) */
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mta2_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/mta2_streamfile.h
index c38d20b59..c8d7d28ba 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/mta2_streamfile.h
+++ b/Frameworks/vgmstream/vgmstream/src/meta/mta2_streamfile.h
@@ -20,14 +20,14 @@ typedef struct {
 } mta2_io_data;
 
 
-static size_t mta2_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, mta2_io_data* data) {
+static size_t mta2_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, mta2_io_data* data) {
     size_t total_read = 0;
     uint32_t (*read_u32)(off_t,STREAMFILE*) = data->big_endian ? read_u32be : read_u32le;
 
 
-
     /* re-start when previous offset (can't map logical<>physical offsets) */
     if (data->logical_offset < 0 || offset < data->logical_offset) {
+        ;VGM_LOG("IO restart: offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset);
         data->physical_offset = data->stream_offset;
         data->logical_offset = 0x00;
         data->data_size = 0;
@@ -45,10 +45,10 @@ static size_t mta2_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset,
         if (data->data_size == 0) {
             uint32_t block_type, block_size, block_track;
 
-            block_type  = read_u32(data->physical_offset+0x00, streamfile); /* subtype and type */
-            block_size  = read_u32(data->physical_offset+0x04, streamfile);
+            block_type  = read_u32(data->physical_offset+0x00, sf); /* subtype and type */
+            block_size  = read_u32(data->physical_offset+0x04, sf);
           //block_unk   = read_u32(data->physical_offset+0x08, streamfile); /* usually 0 except for 0xF0 'end' block */
-            block_track = read_u32(data->physical_offset+0x0c, streamfile);
+            block_track = read_u32(data->physical_offset+0x0c, sf);
 
             if (block_type != data->target_type || block_size == 0xFFFFFFFF)
                 break;
@@ -77,7 +77,7 @@ static size_t mta2_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t 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);
+            bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, sf);
 
             total_read += bytes_done;
             dest += bytes_done;
@@ -96,7 +96,7 @@ static size_t mta2_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset,
 static size_t mta2_io_size(STREAMFILE *streamfile, mta2_io_data* data) {
     uint8_t buf[1];
 
-    if (data->logical_size)
+    if (data->logical_size > 0)
         return data->logical_size;
 
     /* force a fake read at max offset, to get max logical_offset (will be reset next read) */
@@ -108,47 +108,28 @@ static size_t mta2_io_size(STREAMFILE *streamfile, mta2_io_data* data) {
 
 /* Handles removing KCE Japan-style blocks in MTA2 streams
  * (these blocks exist in most KCEJ games and aren't actually related to audio) */
-static STREAMFILE* setup_mta2_streamfile(STREAMFILE *streamFile, off_t stream_offset, int big_endian, const char* extension) {
-    STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
+static STREAMFILE* setup_mta2_streamfile(STREAMFILE *sf, off_t stream_offset, int big_endian, const char *extension) {
+    STREAMFILE *new_sf = NULL;
     mta2_io_data io_data = {0};
-    size_t io_data_size = sizeof(mta2_io_data);
     uint32_t (*read_u32)(off_t,STREAMFILE*) = big_endian ? read_u32be : read_u32le;
 
 
     /* blocks must start with a 'new sub-stream' id */
-    if (read_u32(stream_offset+0x00, streamFile) != 0x00000010)
-        goto fail;
+    if (read_u32(stream_offset+0x00, sf) != 0x00000010)
+        return NULL;
 
-    io_data.target_type = read_u32(stream_offset + 0x0c, streamFile);
+    io_data.target_type = read_u32(stream_offset + 0x0c, sf);
     io_data.stream_offset = stream_offset + 0x10;
-    io_data.stream_size = get_streamfile_size(streamFile) - io_data.stream_offset;
+    io_data.stream_size = get_streamfile_size(sf) - io_data.stream_offset;
     io_data.big_endian = big_endian;
     io_data.logical_offset = -1; /* force phys offset reset */
 
     /* setup subfile */
-    new_streamFile = open_wrap_streamfile(streamFile);
-    if (!new_streamFile) goto fail;
-    temp_streamFile = new_streamFile;
-
-    new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, mta2_io_read,mta2_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;
-
-    if (extension) {
-        new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,extension);
-        if (!new_streamFile) goto fail;
-        temp_streamFile = new_streamFile;
-    }
-
-    return temp_streamFile;
-
-fail:
-    close_streamfile(temp_streamFile);
-    return NULL;
+    new_sf = open_wrap_streamfile(sf);
+    new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(mta2_io_data), mta2_io_read, mta2_io_size);
+    if (extension)
+        new_sf = open_fakename_streamfile_f(new_sf, NULL, extension);
+    return new_sf;
 }
 
 #endif /* _MTA2_STREAMFILE_H_ */
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/nds_sad.c b/Frameworks/vgmstream/vgmstream/src/meta/nds_sad.c
index af3c72621..944cf04b0 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/nds_sad.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/nds_sad.c
@@ -1,54 +1,32 @@
 #include "meta.h"
 #include "../util.h"
 
-/* sadl (only the Professor Layton interleaved IMA version) */
+/* sadl - from DS games with Procyon Studio audio driver */
 VGMSTREAM * init_vgmstream_sadl(STREAMFILE *streamFile) {
     VGMSTREAM * vgmstream = NULL;
-    char filename[PATH_LIMIT];
     off_t start_offset;
+    int loop_flag, channel_count;
 
-    int loop_flag;
-	int channel_count;
-    int coding_type;
 
-    /* check extension, case insensitive */
-    streamFile->get_name(streamFile,filename,sizeof(filename));
-    if (strcasecmp("sad",filename_extension(filename))) goto fail;
+    /* checks */
+    if (!check_extensions(streamFile, "sad"))
+        goto fail;
 
-    /* check header */
     if (read_32bitBE(0x00,streamFile) != 0x7361646c) /* "sadl" */
         goto fail;
-
-    /* check file size */
-    if (read_32bitLE(0x40,streamFile) != get_streamfile_size(streamFile) )
+    if (read_32bitLE(0x40,streamFile) != get_streamfile_size(streamFile))
         goto fail;
 
-    /* check coding type */
-    switch (read_8bit(0x33,streamFile)&0xf0)
-    {
-        case 0x70:
-            coding_type = coding_IMA_int;
-            break;
-        case 0xb0:
-            coding_type = coding_NDS_PROCYON;
-            break;
-        default:
-            goto fail;
-    }
 
     loop_flag = read_8bit(0x31,streamFile);
     channel_count = read_8bit(0x32,streamFile);
+    start_offset = 0x100;
     
-	/* build the VGMSTREAM */
+    /* build the VGMSTREAM */
     vgmstream = allocate_vgmstream(channel_count,loop_flag);
     if (!vgmstream) goto fail;
 
-	/* fill in the vital statistics */
-    start_offset = 0x100;
-	vgmstream->channels = channel_count;
-
-    switch (read_8bit(0x33,streamFile) & 6)
-    {
+    switch (read_8bit(0x33,streamFile) & 6) {
         case 4:
             vgmstream->sample_rate = 32728;
             break;
@@ -59,52 +37,37 @@ VGMSTREAM * init_vgmstream_sadl(STREAMFILE *streamFile) {
             goto fail;
     }
 
-    vgmstream->coding_type = coding_type;
-
-    if (coding_type == coding_IMA_int)
-        vgmstream->num_samples = 
-            (read_32bitLE(0x40,streamFile)-start_offset)/channel_count*2;
-    else if (coding_type == coding_NDS_PROCYON)
-        vgmstream->num_samples = 
-            (read_32bitLE(0x40,streamFile)-start_offset)/channel_count/16*30;
-
-    vgmstream->interleave_block_size=0x10;
-
-    if (loop_flag)
-    {
-        if (coding_type == coding_IMA_int)
-            vgmstream->loop_start_sample = (read_32bitLE(0x54,streamFile)-start_offset)/channel_count*2;
-        else
-            vgmstream->loop_start_sample = (read_32bitLE(0x54,streamFile)-start_offset)/channel_count/16*30;
-        vgmstream->loop_end_sample = vgmstream->num_samples;
-    }
-
-    if (channel_count > 1)
-        vgmstream->layout_type = layout_interleave;
-    else
-        vgmstream->layout_type = layout_none;
     vgmstream->meta_type = meta_SADL;
 
-    /* open the file for reading */
-    {
-        int i;
-        STREAMFILE * file;
-        file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
-        if (!file) goto fail;
-        for (i=0;i<channel_count;i++) {
-            vgmstream->ch[i].streamfile = file;
+    vgmstream->layout_type = layout_interleave;
+    vgmstream->interleave_block_size = 0x10;
 
-            vgmstream->ch[i].channel_start_offset=
-                vgmstream->ch[i].offset=start_offset+
-                vgmstream->interleave_block_size*i;
+    switch(read_8bit(0x33,streamFile) & 0xf0) {
+        case 0x70: /* Ni no Kuni (DS), Professor Layton and the Curious Village (DS), Soma Bringer (DS) */
+            vgmstream->coding_type = coding_IMA_int;
 
-        }
+            vgmstream->num_samples = (read_32bitLE(0x40,streamFile)-start_offset)/channel_count*2;
+            vgmstream->loop_start_sample = (read_32bitLE(0x54,streamFile)-start_offset)/channel_count*2;
+            vgmstream->loop_end_sample = vgmstream->num_samples;
+            break;
+
+        case 0xb0: /* Soma Bringer (DS), Rekishi Taisen Gettenka (DS) */
+            vgmstream->coding_type = coding_NDS_PROCYON;
+
+            vgmstream->num_samples = (read_32bitLE(0x40,streamFile)-start_offset)/channel_count/16*30;
+            vgmstream->loop_start_sample = (read_32bitLE(0x54,streamFile)-start_offset)/channel_count/16*30;
+            vgmstream->loop_end_sample = vgmstream->num_samples;
+            break;
+
+        default:
+            goto fail;
     }
 
+    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;
 }
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps2_2pfs.c b/Frameworks/vgmstream/vgmstream/src/meta/ps2_2pfs.c
index 24df33351..087d2d1a5 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/ps2_2pfs.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/ps2_2pfs.c
@@ -1,56 +1,42 @@
 #include "meta.h"
-#include "../util.h"
+#include "../coding/coding.h"
 
-/* 2PFS (Konami)
-    - Mahoromatic: Moetto - KiraKira Maid-San (PS2) [.2pfs (V1, 2003)]
-    - GANTZ The Game (PS2) [.sap (V2, 2005)]
 
-    There are two versions of the format, though they use different extensions.
-    Implemented both versions here in case there are .2pfs with the V2 header out there.
-    Both loop correctly AFAIK (there is a truncated Mahoromatic rip around, beware).
-*/
-VGMSTREAM * init_vgmstream_ps2_2pfs(STREAMFILE *streamFile) 
-{
+/* 2PFS - from Konami Games [Mahoromatic: Moetto - KiraKira Maid-San (PS2), GANTZ The Game (PS2)] */
+VGMSTREAM * init_vgmstream_ps2_2pfs(STREAMFILE *streamFile) {
     VGMSTREAM * vgmstream = NULL;
-    char filename[PATH_LIMIT];
-    
-    off_t start_offset = 0x800;
-    int interleave = 0x1000;
-
-	int loop_flag;
-	int channel_count;
-	int version; /* v1=1, v2=2 */
-
-    int loop_start_block; /* block number where the loop starts */
-    int loop_end_block; /* usually the last block */
-    int loop_start_sample_adjust; /* loops start/end a few samples into the start/end block */
-    int loop_end_sample_adjust;
+    off_t start_offset;
+    int loop_flag, channel_count, version, interleave;
+    int loop_start_block, loop_end_block; /* block number */
+    int loop_start_adjust, loop_end_adjust; /* loops start/end a few samples into the start/end block */
 
 
-    /* check extension, case insensitive */
-    streamFile->get_name(streamFile,filename,sizeof(filename));
-    if ( strcasecmp("2pfs",filename_extension(filename))
-         && strcasecmp("sap",filename_extension(filename))  )
+    /* checks */
+    /* .sap: standard
+     * .2psf: header id? (Mahoromatic) */
+    if (!check_extensions(streamFile, "sap,2psf"))
         goto fail;
 
-    /* check header ("2PFS") */
-    if (read_32bitBE(0x00,streamFile) != 0x32504653)
+    if (read_32bitBE(0x00,streamFile) != 0x32504653) /* "2PFS" */
         goto fail;
 
     version = read_16bitLE(0x04,streamFile);
-    if ( version!=0x01 && version!=0x02 )
+    if (version != 0x01 && version != 0x02) /* v1: Mahoromatic, v2: Gantz */
         goto fail;
 
 
     channel_count = read_8bit(0x40,streamFile);
     loop_flag = read_8bit(0x41,streamFile);
+    start_offset = 0x800;
+    interleave = 0x1000;
+
     /* other header values
-     *  0x06 (4): unknown, v1=0x0004 v2=0x0001
-     *  0x08 (32): unique file id
-     *  0x0c (32): base header size (v1=0x50, v2=0x60) + datasize (without the 0x800 full header size)
+     *  0x06: unknown, v1=0x0004 v2=0x0001
+     *  0x08: unique file id
+     *  0x0c: base header size (v1=0x50, v2=0x60) + datasize (without the 0x800 full header size)
      *  0x10-0x30: unknown (v1 differs from v2)
      *  0x38-0x40: unknown (v1 same as v2)
-     *  0x4c (32) in V2: unknown, some kind of total samples?
+     *  0x4c: unknown, some kind of total samples? (v2 only)
      */
 
 
@@ -58,59 +44,40 @@ VGMSTREAM * init_vgmstream_ps2_2pfs(STREAMFILE *streamFile)
     vgmstream = allocate_vgmstream(channel_count,loop_flag);
     if (!vgmstream) goto fail;
 
-    /* fill in the vital statistics */
-    vgmstream->channels = channel_count;
-    vgmstream->coding_type = coding_PSX;
+    vgmstream->meta_type = meta_PS2_2PFS;
     vgmstream->num_samples = read_32bitLE(0x34,streamFile) * 28 / 16 / channel_count;
+    vgmstream->coding_type = coding_PSX;
     vgmstream->layout_type = layout_interleave;
     vgmstream->interleave_block_size = interleave;
-    vgmstream->meta_type = meta_PS2_2PFS;
 
-    if ( version==0x01 ) {
+    if (version == 0x01) {
         vgmstream->sample_rate = read_32bitLE(0x44,streamFile);
-        loop_start_sample_adjust = read_16bitLE(0x42,streamFile);
+        loop_start_adjust = read_16bitLE(0x42,streamFile);
         loop_start_block = read_32bitLE(0x48,streamFile);
         loop_end_block = read_32bitLE(0x4c,streamFile);
-    } else {
+    }
+    else {
         vgmstream->sample_rate = read_32bitLE(0x48,streamFile);
-        loop_start_sample_adjust = read_32bitLE(0x44,streamFile);
+        loop_start_adjust = read_32bitLE(0x44,streamFile);
         loop_start_block = read_32bitLE(0x50,streamFile);
         loop_end_block = read_32bitLE(0x54,streamFile);
     }
-    loop_end_sample_adjust = interleave; /* loops end after all samples in the end_block AFAIK */
+    loop_end_adjust = interleave; /* loops end after all samples in the end_block AFAIK */
 
-    if ( loop_flag ) {
-        /* block to offset > offset to sample + adjust (number of samples into the block) */
-        vgmstream->loop_start_sample = ((loop_start_block * channel_count * interleave)
-                * 28 / 16 / channel_count)
-                + (loop_start_sample_adjust * 28 / 16);
-        vgmstream->loop_end_sample = ((loop_end_block * channel_count * interleave)
-                * 28 / 16 / channel_count)
-                + (loop_end_sample_adjust * 28 / 16);
-    }
-
-
-
-    /* open the file for reading */
-    {
-        int i;
-        STREAMFILE * file;
-        file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
-        if (!file) goto fail;
-        
-		for (i=0;i<channel_count;i++) 
-		{
-            vgmstream->ch[i].streamfile = file;
-
-            vgmstream->ch[i].channel_start_offset =
-                vgmstream->ch[i].offset =
-                        start_offset + (vgmstream->interleave_block_size * i);
-        }
+    if (loop_flag) {
+        /* block to offset > offset to sample + adjust (number of frames into the block) */
+        vgmstream->loop_start_sample =
+                ps_bytes_to_samples(loop_start_block * channel_count * interleave, channel_count)
+                + ps_bytes_to_samples(loop_start_adjust * channel_count, channel_count);
+        vgmstream->loop_end_sample =
+                ps_bytes_to_samples(loop_end_block * channel_count * interleave, channel_count)
+                + ps_bytes_to_samples(loop_end_adjust * channel_count, channel_count);
     }
 
+    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);
     return NULL;
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/rwsd.c b/Frameworks/vgmstream/vgmstream/src/meta/rwsd.c
index 7dd87f089..7c7b29b0c 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/rwsd.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/rwsd.c
@@ -129,16 +129,18 @@ VGMSTREAM * init_vgmstream_rwsd(STREAMFILE *streamFile) {
         {
             if (strcasecmp("rwav",ext))
             {
-                if (strcasecmp("bcwav",ext) && strcasecmp("bms",ext))
-                {
-                    goto fail;
-                }
-                else
-                {
-                    // cwav, similar to little endian rwav
-                    rwav = 1;
+                /* .bcwav: standard
+                 * .bms: ?
+                 * .sfx: Wizdom (3DS)
+                 * .str: Pac-Man and the Ghostly Adventures 2 (3DS)
+                 * .zic: Wizdom (3DS) */
+                if (check_extensions(streamFile, "bcwav,bms,sfx,str,zic")) {
+                    rwav = 1; // cwav, similar to little endian rwav
                     big_endian = 0;
                 }
+                else {
+                    goto fail;
+                }
             }
             else
             {
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sgxd.c b/Frameworks/vgmstream/vgmstream/src/meta/sgxd.c
index cc9c0f36c..b1cb9a7fc 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/sgxd.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/sgxd.c
@@ -112,7 +112,7 @@ VGMSTREAM * init_vgmstream_sgxd(STREAMFILE *streamFile) {
         read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamHeader);
 
     switch (type) {
-#ifdef VGM_USE_FFMPEG
+#ifdef VGM_USE_VORBIS
         case 0x02:      /* Ogg Vorbis [Ni no Kuni: Wrath of the White Witch Remastered (PC)] (codec hijack?) */
             vgmstream->codec_data = init_ogg_vorbis(streamFile, start_offset, stream_size, NULL);
             if (!vgmstream->codec_data) goto fail;
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/stx.c b/Frameworks/vgmstream/vgmstream/src/meta/stx.c
deleted file mode 100644
index 3c8f3f922..000000000
--- a/Frameworks/vgmstream/vgmstream/src/meta/stx.c
+++ /dev/null
@@ -1,70 +0,0 @@
-#include "meta.h"
-#include "../util.h"
-
-VGMSTREAM * init_vgmstream_stx(STREAMFILE *streamFile) {
-    VGMSTREAM * vgmstream = NULL;
-    char filename[PATH_LIMIT];
-
-    const int loop_flag = 0;
-    const int channel_count = 2;    /* .stx seems to be stereo only */
-
-    /* check extension, case insensitive */
-    streamFile->get_name(streamFile,filename,sizeof(filename));
-    if (strcasecmp("stx",filename_extension(filename))) goto fail;
-
-    /* length of data */
-    if (read_32bitBE(0x00,streamFile) !=
-        get_streamfile_size(streamFile) - 0x20) goto fail;
-
-    /* bits per sample? */
-    if (read_16bitBE(0x0a,streamFile) != 4) goto fail;
-
-    /* samples per frame? */
-    if (read_16bitBE(0x0c,streamFile) != 0x10) goto fail;
-
-    /* ?? */
-    if (read_16bitBE(0x0e,streamFile) != 0x1E) goto fail;
-
-    /* build the VGMSTREAM */
-
-    vgmstream = allocate_vgmstream(channel_count,loop_flag);
-    if (!vgmstream) goto fail;
-
-
-    /* fill in the vital statistics */
-    vgmstream->num_samples = read_32bitBE(0x04,streamFile);
-    vgmstream->sample_rate = (uint16_t)read_16bitBE(0x08,streamFile);
-    /* channels and loop flag are set by allocate_vgmstream */
-
-    vgmstream->coding_type = coding_NGC_AFC;
-    vgmstream->layout_type = layout_interleave;
-    vgmstream->meta_type = meta_STX;
-
-    /* frame-level interleave (9 bytes) */
-    vgmstream->interleave_block_size = 9;
-
-    /* open the file for reading by each channel */
-    {
-        STREAMFILE *chstreamfile;
-        int i;
-
-        /* both channels use same buffer, as interleave is so small */
-        chstreamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
-        if (!chstreamfile) goto fail;
-
-        for (i=0;i<channel_count;i++) {
-            vgmstream->ch[i].streamfile = chstreamfile;
-
-            vgmstream->ch[i].channel_start_offset=
-                vgmstream->ch[i].offset=
-                0x20 + i*vgmstream->interleave_block_size;
-        }
-    }
-
-    return vgmstream;
-
-    /* clean up anything we may have opened */
-fail:
-    if (vgmstream) close_vgmstream(vgmstream);
-    return NULL;
-}
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txth.c b/Frameworks/vgmstream/vgmstream/src/meta/txth.c
index eb307fad5..d8c869248 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/txth.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/txth.c
@@ -1058,6 +1058,9 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char
     /* COEFS */
     else if (is_string(key,"coef_offset")) {
         if (!parse_num(txth->streamHead,txth,val, &txth->coef_offset)) goto fail;
+        /* special adjustment */
+        if (txth->subsong_offset)
+            txth->coef_offset = txth->base_offset + txth->coef_offset + txth->subsong_offset * (txth->target_subsong - 1);
     }
     else if (is_string(key,"coef_spacing")) {
         if (!parse_num(txth->streamHead,txth,val, &txth->coef_spacing)) goto fail;
@@ -1081,6 +1084,9 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char
     else if (is_string(key,"hist_offset")) {
         if (!parse_num(txth->streamHead,txth,val, &txth->hist_offset)) goto fail;
         txth->hist_set = 1;
+        /* special adjustment */
+        if (txth->subsong_offset)
+            txth->hist_offset = txth->base_offset + txth->hist_offset + txth->subsong_offset * (txth->target_subsong - 1);
     }
     else if (is_string(key,"hist_spacing")) {
         if (!parse_num(txth->streamHead,txth,val, &txth->hist_spacing)) goto fail;
@@ -1103,9 +1109,9 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char
     else if (is_string(key,"name_offset")) {
         if (!parse_num(txth->streamHead,txth,val, &txth->name_offset)) goto fail;
         txth->name_offset_set = 1;
-        /* special subsong adjustment */
+        /* special adjustment */
         if (txth->subsong_offset)
-            txth->name_offset = txth->name_offset + txth->subsong_offset * (txth->target_subsong - 1);
+            txth->name_offset = txth->base_offset + txth->name_offset + txth->subsong_offset * (txth->target_subsong - 1);
     }
     else if (is_string(key,"name_size")) {
         if (!parse_num(txth->streamHead,txth,val, &txth->name_size)) goto fail;
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c
index c6309982c..7d44c6353 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c
@@ -1013,10 +1013,8 @@ fail:
 }
 
 static int parse_type_silence(ubi_bao_header * bao, off_t offset, STREAMFILE* streamFile) {
-    int32_t (*read_32bit)(off_t,STREAMFILE*) = bao->big_endian ? read_32bitBE : read_32bitLE;
+    float (*read_f32)(off_t,STREAMFILE*) = bao->big_endian ? read_f32be : read_f32le;
     off_t h_offset = offset + bao->header_skip;
-    uint32_t duration_int;
-    float* duration_float;
 
     /* silence header */
     bao->type = UBI_SILENCE;
@@ -1025,13 +1023,8 @@ static int parse_type_silence(ubi_bao_header * bao, off_t offset, STREAMFILE* st
         goto fail;
     }
 
-    {
-        duration_int = (uint32_t)read_32bit(h_offset + bao->cfg.silence_duration_float, streamFile);
-        duration_float = (float*)&duration_int;
-        bao->duration = *duration_float;
-    }
-
-    if (bao->duration <= 0) {
+    bao->duration = read_f32(h_offset + bao->cfg.silence_duration_float, streamFile);
+    if (bao->duration <= 0.0f) {
         VGM_LOG("UBI BAO: bad duration %f at %x\n", bao->duration, (uint32_t)offset);
         goto fail;
     }
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c
index fe3cb3cc1..5bfdd28c7 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c
@@ -1369,9 +1369,8 @@ fail:
 }
 
 static int parse_type_silence(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) {
+    float (*read_f32)(off_t,STREAMFILE*) = sb->big_endian ? read_f32be : read_f32le;
     int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE;
-    uint32_t duration_int;
-    float* duration_float;
 
     /* silence header */
     sb->type = UBI_SILENCE;
@@ -1381,13 +1380,11 @@ static int parse_type_silence(ubi_sb_header * sb, off_t offset, STREAMFILE* stre
     }
 
     if (sb->cfg.silence_duration_int) {
-        duration_int = (uint32_t)read_32bit(offset + sb->cfg.silence_duration_int, streamFile);
+        uint32_t duration_int = (uint32_t)read_32bit(offset + sb->cfg.silence_duration_int, streamFile);
         sb->duration = (float)duration_int / 65536.0f; /* 65536.0 is common so probably means 1.0 */
     }
     else if (sb->cfg.silence_duration_float) {
-        duration_int = (uint32_t)read_32bit(offset + sb->cfg.silence_duration_float, streamFile);
-        duration_float = (float*)&duration_int;
-        sb->duration = *duration_float;
+        sb->duration = read_f32(offset + sb->cfg.silence_duration_float, streamFile);
     }
 
     return 1;
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/vag.c b/Frameworks/vgmstream/vgmstream/src/meta/vag.c
index b3eb80b96..cbb2cc160 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/vag.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/vag.c
@@ -146,20 +146,6 @@ VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile) {
 
                 loop_flag = ps_find_loop_offsets(streamFile, start_offset, channel_size*channel_count, channel_count, interleave, &loop_start_sample, &loop_end_sample);
             }
-            else if (read_32bitBE(0x30,streamFile) == 0x56414770) { /* "VAGp" */
-                /* The Red Star (PS2) */
-                start_offset = 0x60; /* two VAGp headers */
-                channel_count = 2;
-
-                if ((file_size - start_offset) % 0x4000 == 0)
-                    interleave = 0x4000;
-                else if ((file_size - start_offset) % 0x4180 == 0)
-                    interleave = 0x4180;
-                else
-                    goto fail;
-
-                loop_flag = 0; /* loop segments */
-            }
             else if (version == 0x40000000) {
                 /* Killzone (PS2) */
                 start_offset = 0x30;
@@ -217,9 +203,32 @@ VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile) {
                 /* Need for Speed: Hot Pursuit 2 (PS2) */
                 start_offset = 0x30;
                 channel_count = read_32bitBE(0x2c, streamFile);
-                interleave = 0x8000;
                 channel_size = channel_size / channel_count;
                 loop_flag = 0;
+
+                /* detect interleave using end markers */
+                interleave = 0;
+
+                if (channel_count > 1) {
+                    off_t offset = file_size;
+                    off_t end_off = 0;
+                    uint8_t flag;
+
+                    while (1) {
+                        offset -= 0x10;
+                        flag = read_8bit(offset + 0x01, streamFile);
+                        if (flag == 0x01) {
+                            if (!end_off) {
+                                end_off = offset;
+                            } else {
+                                interleave = end_off - offset;
+                                break;
+                            }
+                        }
+
+                        if (offset == start_offset) goto fail;
+                    }
+                }
             }
             else {
                 /* standard PS1/PS2/PS3 .vag [Ecco the Dolphin (PS2), Legasista (PS3)] */
@@ -267,3 +276,59 @@ fail:
     close_vgmstream(vgmstream);
     return NULL;
 }
+
+/* AAAp - Acclaim Austin Audio VAG header [The Red Star (PS2)] */
+VGMSTREAM* init_vgmstream_vag_aaap(STREAMFILE* streamFile) {
+    VGMSTREAM* vgmstream = NULL;
+    off_t vag_offset, start_offset;
+    uint32_t channel_size, sample_rate;
+    uint16_t interleave, channels;
+    uint32_t i;
+    int loop_flag;
+
+    /* checks */
+    /* .vag - assumed, we don't know the original filenames */
+    if (!check_extensions(streamFile, "vag"))
+        goto fail;
+
+    if (read_u32be(0x00, streamFile) != 0x41414170) /* "AAAp" */
+        goto fail;
+
+    interleave = read_u16le(0x04, streamFile);
+    channels = read_u16le(0x06, streamFile);
+    vag_offset = 0x08;
+
+    /* file has VAGp header for each channel */
+    for (i = 0; i < channels; i++) {
+        if (read_u32be(vag_offset + i * 0x30, streamFile) != 0x56414770) /* "VAGp" */
+            goto fail;
+    }
+    
+    /* check version */
+    if (read_u32be(vag_offset + 0x04, streamFile) != 0x20)
+        goto fail;
+
+    channel_size = read_u32be(vag_offset + 0x0c, streamFile);
+    sample_rate = read_u32be(vag_offset + 0x10, streamFile);
+    start_offset = vag_offset + channels * 0x30;
+    loop_flag = 0;
+
+    /* build the VGMSTREAM */
+    vgmstream = allocate_vgmstream(channels, loop_flag);
+    if (!vgmstream) goto fail;
+
+    vgmstream->meta_type = meta_PS2_VAGp_AAAP;
+    vgmstream->sample_rate = sample_rate;
+    vgmstream->num_samples = ps_bytes_to_samples(channel_size, 1);
+    vgmstream->coding_type = coding_PSX;
+    vgmstream->layout_type = layout_interleave;
+    vgmstream->interleave_block_size = interleave;
+
+    if (!vgmstream_open_stream(vgmstream, streamFile, start_offset))
+        goto fail;
+    return vgmstream;
+
+fail:
+    close_vgmstream(vgmstream);
+    return NULL;
+}
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/wave.c b/Frameworks/vgmstream/vgmstream/src/meta/wave.c
index fe6c69453..3b7a761f7 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/wave.c
+++ b/Frameworks/vgmstream/vgmstream/src/meta/wave.c
@@ -8,10 +8,9 @@ VGMSTREAM * init_vgmstream_wave(STREAMFILE *streamFile) {
     int loop_flag = 0, channel_count, sample_rate, codec;
     int32_t num_samples, loop_start = 0, loop_end = 0;
     size_t interleave;
-
     int big_endian;
     int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
-    //int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
+    float (*read_f32)(off_t,STREAMFILE*) = NULL;
 
     /* checks */
     if (!check_extensions(streamFile, "wave"))
@@ -27,10 +26,10 @@ VGMSTREAM * init_vgmstream_wave(STREAMFILE *streamFile) {
     big_endian = read_32bitBE(0x00,streamFile) == 0xE5B7ECFE;
     if (big_endian) {
         read_32bit = read_32bitBE;
-        //read_16bit = read_16bitBE;
+        read_f32 = read_f32be;
     } else {
         read_32bit = read_32bitLE;
-        //read_16bit = read_16bitLE;
+        read_f32 = read_f32le;
     }
 
     channel_count = read_8bit(0x05,streamFile);
@@ -40,15 +39,7 @@ VGMSTREAM * init_vgmstream_wave(STREAMFILE *streamFile) {
     if (read_8bit(0x0c,streamFile) != 0x00) /* ? */
         goto fail;
 
-    /* sample rate in 32b float (WHY?)*/
-    {
-        uint32_t sample_int = (uint32_t)read_32bit(0x0c, streamFile);
-        float* sample_float;
-        sample_float = (float*)&sample_int;
-
-        sample_rate = (int)(*sample_float);
-    }
-
+    sample_rate = (int)read_f32(0x0c, streamFile); /* sample rate in 32b float (WHY?) */
     num_samples = read_32bit(0x10, streamFile);
     loop_start  = read_32bit(0x14, streamFile);
     loop_end    = read_32bit(0x18, streamFile);
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xssb.c b/Frameworks/vgmstream/vgmstream/src/meta/xssb.c
new file mode 100644
index 000000000..8f63c793a
--- /dev/null
+++ b/Frameworks/vgmstream/vgmstream/src/meta/xssb.c
@@ -0,0 +1,140 @@
+#include "meta.h"
+#include "../coding/coding.h"
+
+
+//todo test and rethink usefulness/viability of globally using this
+/* generic helper with a usable fields to describe static header values */
+typedef struct {
+    int codec;
+    int type;
+    int channels;
+    int sample_rate;
+    int loop_flag;
+    int loop_start;
+    int loop_end;
+
+    int total_subsongs;
+    int target_subsong;
+
+    size_t file_size;
+
+    off_t info_start;
+
+    off_t header_start;
+    size_t header_entry;
+    off_t header_offset;
+
+    off_t data_start;
+    size_t data_size;
+
+    off_t stream_start;
+    size_t stream_size;
+} header_t;
+
+/* XSSB - from Artoon games [Blinx (Xbox), Blinx 2 (Xbox)] */
+VGMSTREAM* init_vgmstream_xssb(STREAMFILE *sf) {
+    VGMSTREAM *vgmstream = NULL;
+    //off_t start_offset, header_offset, data_start, info_start, header_start;
+    //size_t header_size, stream_size;
+    //int loop_flag, channel_count, sample_rate, codec, loop_start, loop_end;
+    //int total_subsongs, target_subsong = streamFile->stream_index;
+    header_t h;
+
+
+    /* checks */
+    /* .bin: from named files inside .ipk bigfiles */
+    if (!check_extensions(sf, "bin,lbin"))
+        goto fail;
+
+    if (read_u32be(0x00, sf) != 0x58535342) /* "XSSB" */
+        goto fail;
+    /* 0x04: null */
+    /* 0x08: date-version ('20011217' in hex) */
+    /* 0x0c: null */
+
+    h.info_start   = read_s32le(0x10, sf);
+    h.header_start = read_s32le(0x14, sf);
+    h.data_start   = read_s32le(0x18, sf);
+    /* 0x1c: null */
+
+    h.header_entry = read_s16le(h.info_start + 0x00, sf);
+    /* 0x02: always 127 */
+
+    /* get subsongs from header entries */
+    {
+        off_t offset = h.header_start;
+
+        h.total_subsongs = 0;
+        h.target_subsong = sf->stream_index <= 0 ? 1 : sf->stream_index;
+
+        h.header_offset  = 0;
+        while (offset < h.data_start) {
+            /* headers are just pasted together and then padding */
+            if (read_u32be(offset, sf) == 0)
+                break;
+            h.total_subsongs++;
+
+            if (h.target_subsong == h.total_subsongs) {
+                h.header_offset = offset;
+            }
+
+            offset += h.header_entry;
+        }
+
+        if (h.header_offset == 0)
+            goto fail;
+        if (h.target_subsong > h.total_subsongs || h.total_subsongs < 1)
+            goto fail;
+    }
+
+    /* read header */
+    h.codec         = read_s16le(h.header_offset + 0x00, sf);
+    h.channels      = read_s16le(h.header_offset + 0x02, sf);
+    h.sample_rate   = read_u16le(h.header_offset + 0x04, sf);
+    /* 0x08: bitrate */
+    /* 0x0c: block align/bps */
+    /* 0x10: 0=PCM, 2=XBOX-IMA? */
+    h.stream_start  = read_s32le(h.header_offset + 0x14, sf) + h.data_start;
+    h.stream_size   = read_s32le(h.header_offset + 0x18, sf);
+    h.loop_start    = read_s32le(h.header_offset + 0x1c, sf);
+    h.loop_end      = read_s32le(h.header_offset + 0x20, sf);
+    /* others: unknown and mostly fixed values */
+    h.loop_flag = (h.loop_end > 0);
+
+
+    /* build the VGMSTREAM */
+    vgmstream = allocate_vgmstream(h.channels, h.loop_flag);
+    if (!vgmstream) goto fail;
+
+    vgmstream->meta_type = meta_XSSB;
+    vgmstream->sample_rate = h.sample_rate;
+    vgmstream->loop_start_sample = h.loop_start;
+    vgmstream->loop_end_sample = h.loop_end;
+    vgmstream->num_streams = h.total_subsongs;
+    vgmstream->stream_size = h.stream_size;
+
+    switch(h.codec) {
+        case 0x01:
+            vgmstream->coding_type = coding_PCM16LE;
+            vgmstream->layout_type = layout_interleave;
+            vgmstream->interleave_block_size = 0x01;
+
+            vgmstream->num_samples = pcm_bytes_to_samples(h.stream_size, h.channels, 16);
+            break;
+
+        case 0x69:
+            vgmstream->coding_type = coding_XBOX_IMA;
+            vgmstream->layout_type = layout_none;
+
+            vgmstream->num_samples = xbox_ima_bytes_to_samples(h.stream_size, h.channels);
+            break;
+    }
+
+    if (!vgmstream_open_stream(vgmstream, sf, h.stream_start))
+        goto fail;
+    return vgmstream;
+
+fail:
+    close_vgmstream(vgmstream);
+    return NULL;
+}
diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h b/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h
index b254a7fe6..7772f4622 100644
--- a/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h
+++ b/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h
@@ -30,6 +30,9 @@ typedef struct {
     int   cue_names_size;
     off_t cue_names_offset;
 
+    int   index_size;
+    int   entry_size;
+
     /* output */
     int parse_done;
     char name[STREAM_NAME_SIZE];
@@ -38,9 +41,16 @@ typedef struct {
 } xsb_header;
 
 
-static void xsb_check_stream(xsb_header * xsb, int stream_index, int wavebank_index, off_t name_offset, STREAMFILE *sf) {
+static void xsb_check_stream(xsb_header *xsb, int stream_index, int wavebank_index, off_t name_offset, STREAMFILE *sf) {
     if (xsb->parse_done)
         return;
+    //;VGM_LOG("XSB old: found stream=%i vs %i, wavebank=%i vs %i, name_offset=%lx\n", stream_index, xsb->selected_stream, wavebank_index, xsb->selected_wavebank, name_offset);
+
+    if (stream_index < 0 || stream_index > 0xFFF || wavebank_index < 0 || wavebank_index > xsb->wavebanks_count) {
+        VGM_LOG("XSB old: bad stream=%i, wavebank=%i\n", stream_index, wavebank_index);
+        return;
+    }
+
 
     /* multiple names may correspond to a stream (ex. Blue Dragon), so we concat all */
     if (xsb->selected_stream == stream_index &&
@@ -48,7 +58,7 @@ static void xsb_check_stream(xsb_header * xsb, int stream_index, int wavebank_in
         char name[STREAM_NAME_SIZE];
         size_t name_size;
 
-        name_size = read_string(name,sizeof(name), name_offset,sf); /* null-terminated */
+        name_size = read_string(name,sizeof(name), name_offset, sf); /* null-terminated */
 
         if (xsb->name_len) {
             const char *cat = "; ";
@@ -68,103 +78,238 @@ static void xsb_check_stream(xsb_header * xsb, int stream_index, int wavebank_in
     }
 }
 
-/* old XACT1 is a bit different and much of it is unknown but this seems to work.
- * - after header is the simple(?) cues table then complex(?) cues table
- * - simple cues point to complex cues by index
- * - complex cues may have stream/wavebank or point again to a sound(?) with the stream/wavebank
- */
-static int parse_xsb_cues_old(xsb_header * xsb, STREAMFILE *sf) {
+
+static int parse_xsb_old_cue_entry(xsb_header *xsb, STREAMFILE *sf, off_t name_offset, int entry) {
     int32_t  (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
     int16_t  (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
-
     uint8_t flags, subflags;
-    int cue_index, stream_index, wavebank_index = 0;
-    off_t offset, name_offset, cue_offset, sound_offset;
-    int i;
-    size_t simple_entry, complex_entry;
+    uint32_t sound_type, sound_size;
+    int stream_index, wavebank_index;
+    off_t offset, jump_offset, sound_offset, min_sections_offset, max_sections_offset;
+    int i, j, sound_count, table_count;
 
 
-    if (xsb->version <= XSB_XACT1_1_MAX) {
-        simple_entry = 0x10;
-        complex_entry = 0x14;
-    }
-    else if (xsb->version <= XSB_XACT1_2_MAX) {
-        simple_entry = 0x14;
-        complex_entry = 0x14;
-    }
-    else {
-        VGM_LOG("XSB: unknown old format for version %x\n", xsb->version);
+    if (entry < 0 || entry > xsb->complex_cues_count) {
+        VGM_LOG("XSB old: ignored bad cue entry %i\n", entry);
         goto fail;
     }
 
+    min_sections_offset = xsb->sounds_offset + xsb->simple_cues_count*xsb->index_size + xsb->complex_cues_offset*xsb->entry_size;
+    max_sections_offset = get_streamfile_size(sf);
 
-    offset = xsb->sounds_offset;
-    for (i = 0; i < xsb->simple_cues_count; i++) {
+    offset = xsb->sounds_offset + xsb->simple_cues_count*xsb->index_size + entry*xsb->entry_size;
 
-        /* *** simple sound *** */
-        /* 00(2): flags? */
-        cue_index   = read_s16(offset + 0x02,sf);
-        name_offset = read_s32(offset + 0x04,sf);
-        /* 06-14: unknown */
 
-        //;VGM_LOG("XSB old simple at %lx: cue=%i, name_offset=%lx\n", offset, cue_index, name_offset);
-        offset += simple_entry;
+    /*** cue entry ***/
+    /* 0x00: offset or stream/wave */
+    /* others: mostly 1 byte fields, probably config for sfx/complex entries */
+    flags = read_u8(offset + 0x0b, sf);
+    //;VGM_LOG("XSB old entry %i at %lx: flags=%x\n", entry, offset, flags);
 
-        /* when cue_index is -1 @0x08 points to some offset (random sound type?) [ex. ATV 3 Lawless (Xbox)] */
-        if (cue_index < 0 && cue_index > xsb->complex_cues_count) {
-            VGM_LOG("XSB old: ignored cue index %i\n", cue_index);
-            continue;
+    if (flags & 0x10) { /* multi entry (found with lower bits but not with 8) */
+        jump_offset = read_s32(offset + 0x00, sf);
+
+        if (jump_offset < min_sections_offset || jump_offset > max_sections_offset) {
+            VGM_LOG("XSB old entry %i at %lx: bad multi jump offset=%lx\n", entry, offset, jump_offset);
+            goto fail;
         }
 
+        /*** table to streams ***/
+        table_count = read_s8(jump_offset + 0x00, sf);
+        /* 0x01: null? */
+        /* 0x02: always count*2? */
+        //;VGM_LOG("XSB old multi stream table at %lx: count=%x\n", jump_offset, table_count);
 
-        /* *** complex sound *** */
-        cue_offset = xsb->sounds_offset + xsb->simple_cues_count*simple_entry + cue_index*complex_entry;
+        for (j = 0; j < table_count; j++) {
+            stream_index    = read_s16(jump_offset + 0x04 + 0x08*j + 0x00, sf);
+            wavebank_index  = read_s16(jump_offset + 0x04 + 0x08*j + 0x02, sf);
+            /* 0x04: config? */
 
-        /* most fields looks like more flags and optional offsets depending of flags */
-        flags = read_u8(cue_offset + 0x0b,sf);
-
-        if (flags & 8) { /* simple */
-            stream_index    = read_s16(cue_offset + 0x00,sf);
-            wavebank_index  = read_s16(cue_offset + 0x02,sf);
+            xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
+            if (xsb->parse_done) return 1;
         }
-        //else if (flags & 4) { /* unsure */
-        //    VGM_LOG("XSB old complex at %lx: unknown flags=%x\n", cue_offset, flags);
-        //    continue;
-        //}
-        else { /* complex (flags none/1/2) */
-            sound_offset = read_s32(cue_offset + 0x00,sf);
+    }
+    else if (flags & 0x8) { /* simple entry (also found with lower bits) */
+        stream_index    = read_s16(offset + 0x00, sf);
+        wavebank_index  = read_s16(offset + 0x02, sf);
 
-            /* *** jump entry *** */
-            /* 00(1): flags? */
-            sound_offset = read_s32(sound_offset + 0x01,sf) & 0x00FFFFFF; /* 24b */
-
-            /* *** sound entry *** */
-            subflags = read_u8(sound_offset + 0x00,sf);
-            if (subflags == 0x00) { /* 0x0c entry */
-                stream_index    = read_s16(sound_offset + 0x08,sf);
-                wavebank_index  = read_s16(sound_offset + 0x0a,sf);
-            }
-            else if (subflags == 0x0a) { /* 0x20 entry */
-                stream_index    = read_s16(sound_offset + 0x1c,sf);
-                wavebank_index  = read_s16(sound_offset + 0x1e,sf);
-            }
-            else {
-                VGM_LOG("XSB old sound at %lx: unknown subflags=%x\n", sound_offset, subflags);
-                continue;
-            }
-        }
-
-        //;VGM_LOG("XSB old complex at %lx: flags=%x, stream=%i, wavebank=%i, name_offset=%lx\n", cue_offset, flags, stream_index, wavebank_index, name_offset);
-        xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+        xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
         if (xsb->parse_done) return 1;
     }
+    else { /* complex entry (lower flags) */
+        jump_offset = read_s32(offset + 0x00, sf);
+
+        if (jump_offset < min_sections_offset || jump_offset > max_sections_offset) {
+            VGM_LOG("XSB old entry %i at %lx: bad complex jump offset=%lx\n", entry, offset, jump_offset);
+            goto fail;
+        }
+
+        /*** sound table ***/
+        sound_count  = read_s8 (jump_offset + 0x00, sf);
+        sound_offset = read_s32(jump_offset + 0x01, sf) & 0x00FFFFFF; /* 24b */
+        //;VGM_LOG("XSB old entry %i sound table at %lx: count=%x\n", entry, jump_offset, sound_count);
+
+        /* read all sounds (seems ordered higher types to lower) */
+        for (i = 0; i < sound_count; i++) {
+            /*** sound entry ***/
+            sound_type = read_u8(sound_offset + 0x00, sf);
+            /* 0x01: rarely set but possible */
+            /* 0x02: null? */
+            sound_size = read_u8(sound_offset + 0x04, sf);
+            //;VGM_LOG("XSB old entry sound %i at %lx: type=%x\n", i, sound_offset, sound_type);
+
+            switch(sound_type) {
+                case 0x12:
+                case 0x11:
+                case 0x10:
+                case 0x07:
+                case 0x05:
+                    /* config? (doesn't seem they contain entries or offsets) */
+                    break;
+#if 0
+                case 0x0a /* used? (0x20 entry)? */
+                    stream_index    = read_s16(sound_offset + 0x1c, sf);
+                    wavebank_index  = read_s16(sound_offset + 0x1e, sf);
+                    break;
+#endif
+
+                case 0x01: /* has more fields, uses subflag 0x04 */
+                case 0x00: /* smaller, uses subflag 0x44 (rare) */
+                    subflags = read_u8(sound_offset + 0x05, sf);
+
+                    if (subflags == 0x00 || subflags == 0x40) {
+                        stream_index    = read_s16(sound_offset + 0x08, sf);
+                        wavebank_index  = read_s16(sound_offset + 0x0a, sf);
+
+                        xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
+                        if (xsb->parse_done) return 1;
+                    }
+                    else if (subflags == 0x04 || subflags == 0x44) {
+                        jump_offset = read_s32(sound_offset + 0x08, sf);
+
+                        if (jump_offset < min_sections_offset || jump_offset > max_sections_offset) {
+                            VGM_LOG("XSB old entry %i at %lx: bad complex multi jump offset=%lx at %lx\n", entry, offset, jump_offset, sound_offset);
+                            break;
+                        }
+
+                        /*** table to streams ***/
+                        table_count = read_s8(jump_offset + 0x00, sf);
+                        /* 0x01: null? */
+                        /* 0x02: always count*2? */
+                        //;VGM_LOG("XSB old complex stream table at %lx: count=%x\n", jump_offset, table_count);
+
+                        for (j = 0; j < table_count; j++) {
+                            stream_index    = read_s16(jump_offset + 0x04 + 0x08*j + 0x00, sf);
+                            wavebank_index  = read_s16(jump_offset + 0x04 + 0x08*j + 0x02, sf);
+                            /* 0x04: config? */
+
+                            xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
+                            if (xsb->parse_done) return 1;
+                        }
+                    }
+                    else {
+                        VGM_LOG("XSB old entry %i at %lx: bad complex multi flags at %lx\n", entry, offset, sound_offset);
+                    }
+                    break;
+
+                    stream_index    = read_s16(sound_offset + 0x08, sf);
+                    wavebank_index  = read_s16(sound_offset + 0x0a, sf);
+
+                    xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
+                    if (xsb->parse_done) return 1;
+                    break;
+
+                default:
+                    VGM_LOG("XSB old entry %i at %lx: unknown sound type=%x at %lx\n", entry, offset, sound_type, sound_offset);
+                    break;
+            }
+
+            sound_offset += 0x04 + 0x04 + sound_size;
+        }
+    }
 
     return 1;
 fail:
     return 0;
 }
 
-static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
+/* old XACT1 is a bit different and much of it is unknown but this seems ok:
+ * - after header is the cue index table then cue entry table
+ * - each cue index points to a cue entry by number
+ * - each cue entry have a stream/wavebank, directly or first pointing to a "sound"
+ * sound entries are more complex with multi-parts and subtables (mainly used for sfx,
+ * ex. ATV 3 Lawless (Xbox), Psychonauts (Xbox) have more complex types.
+ *
+ * Some streams may not be pointed at all as they don't have an apparent name, or have an
+ * entry in the sound table but no reference to it (ex. CommonMusic.xsb or BBFX.xsb in Psychonauts)
+ *
+ * Data is divided like:
+ * - header
+ * - cue indexes
+ * - cue entries
+ * - wavebank names
+ * - cue names
+ * - unknown table
+ * - sounds jump table
+ * - sounds entries
+ * - multi entry jump table
+ * - others
+ */
+static int parse_xsb_old_cues(xsb_header *xsb, STREAMFILE *sf) {
+    int32_t  (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
+    int16_t  (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
+    uint16_t flags;
+    int cue_entry;
+    off_t offset, name_offset, jump_offset;
+    int i, j, table_count;
+
+
+    //;VGM_LOG("XSB old: s.offset=%lx, index count=%i, entry count=%i\n", xsb->sounds_offset, xsb->simple_cues_count, xsb->complex_cues_count);
+
+    offset = xsb->sounds_offset;
+    for (i = 0; i < xsb->simple_cues_count; i++) {
+
+        /*** cue index ***/
+        flags       = read_s16(offset + 0x00, sf); /* 0 is normal, 2 exists and 8 often goes with -1 (random) entry */
+        cue_entry   = read_s16(offset + 0x02, sf);
+        name_offset = read_s32(offset + 0x04, sf);
+        /* 0x08: table offset, or -1 */
+        /* 0x0c: some low value or flag? */
+        /* 0x0e: some index? */
+        /* 0x10: 4 fields? (-1 or 7) */
+        //;VGM_LOG("XSB old index %i at %lx: flags=%x, entry=%i, name_offset=%lx\n", i, offset, flags, cue_entry, name_offset);
+
+        if (cue_entry < 0) {
+            jump_offset = read_s32(offset + 0x08, sf);
+            /* 0x0c/0e: some count? */
+            /* 0x10: offset to some empty-ish table */
+
+            /*** table (random?) to cue entry ***/
+            table_count = read_s8(jump_offset + 0x00, sf);
+            /* 0x01: often 0x60? */
+            /* 0x02: always count*2? */
+            //;VGM_LOG("XSB old entry table at %lx: count=%x\n", jump_offset, table_count);
+
+            for (j = 0; j < table_count; j++) {
+                cue_entry = read_s16(jump_offset + 0x04 + 0x08*j, sf);
+                /* 0x02: null? */
+                /* 0x04/6: related to randomness? */
+                parse_xsb_old_cue_entry(xsb, sf, name_offset, cue_entry);
+                if (xsb->parse_done) return 1;
+            }
+        }
+        else {
+            parse_xsb_old_cue_entry(xsb, sf, name_offset, cue_entry);
+            if (xsb->parse_done) return 1;
+        }
+
+        offset += xsb->index_size;
+    }
+
+    return 1;
+}
+
+static int parse_xsb_clip(xsb_header *xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
     uint32_t (*read_u32)(off_t,STREAMFILE*) = xsb->big_endian ? read_u32be : read_u32le;
     int16_t  (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
 
@@ -173,13 +318,13 @@ static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STR
     int i, t, track_count, event_count;
 
 
-    event_count = read_s8(offset + 0x00,sf);
+    event_count = read_s8(offset + 0x00, sf);
 
     //;VGM_LOG("XSB clip at %lx\n", offset);
     offset += 0x01;
 
     for (i = 0; i < event_count; i++) {
-        flags = read_u32(offset + 0x00,sf);
+        flags = read_u32(offset + 0x00, sf);
         /* 04(2): random offset */
 
         //;VGM_LOG("XSB clip event: %x at %lx\n", flags, offset);
@@ -190,8 +335,8 @@ static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STR
             case 0x01: /* playwave event */
                 /* 00(1): unknown */
                 /* 01(1): flags */
-                stream_index    = read_s16(offset + 0x02,sf);
-                wavebank_index  = read_s8 (offset + 0x04,sf);
+                stream_index    = read_s16(offset + 0x02, sf);
+                wavebank_index  = read_s8 (offset + 0x04, sf);
                 /* 05(1): loop count */
                 /* 06(2): pan angle */
                 /* 08(2): pan arc */
@@ -199,7 +344,7 @@ static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STR
                 //;VGM_LOG("XSB clip event 1 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
                 offset += 0x0a;
 
-                xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+                xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
                 if (xsb->parse_done) return 1;
                 break;
 
@@ -209,7 +354,7 @@ static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STR
                 /* 02(1): loop count */
                 /* 03(2): pan angle */
                 /* 05(2): pan arc */
-                track_count = read_s16(offset + 0x07,sf);
+                track_count = read_s16(offset + 0x07, sf);
                 /* 09(1): flags? */
                 /* 0a(5): unknown */
 
@@ -217,15 +362,15 @@ static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STR
                 offset += 0x0F;
 
                 for (t = 0; t < track_count; t++) {
-                    stream_index    = read_s16(offset + 0x00,sf);
-                    wavebank_index  = read_s8 (offset + 0x02,sf);
+                    stream_index    = read_s16(offset + 0x00, sf);
+                    wavebank_index  = read_s8 (offset + 0x02, sf);
                     /* 03(1): min weight */
                     /* 04(1): min weight */
 
                     //;VGM_LOG("XSB clip event 3: track=%i, stream=%i, wavebank=%i\n", t, stream_index, wavebank_index);
                     offset += 0x05;
 
-                    xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+                    xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
                     if (xsb->parse_done) return 1;
                 }
                 break;
@@ -233,8 +378,8 @@ static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STR
             case 0x04: /* playwave event */
                 /* 00(1): unknown */
                 /* 01(1): flags */
-                stream_index    = read_s16(offset + 0x02,sf);
-                wavebank_index  = read_s8 (offset + 0x04,sf);
+                stream_index    = read_s16(offset + 0x02, sf);
+                wavebank_index  = read_s8 (offset + 0x04, sf);
                 /* 05(1): loop count */
                 /* 06(2): pan angle */
                 /* 08(2): pan arc */
@@ -252,7 +397,7 @@ static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STR
                 //;VGM_LOG("XSB clip event 4 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index);
                 offset += 0x1c;
 
-                xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+                xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
                 if (xsb->parse_done) return 1;
                 break;
 
@@ -272,7 +417,7 @@ static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STR
                 /* 16(1): max Q */
                 /* 17(1): unknown */
                 /* 18(1): variation flags */
-                track_count = read_s16(offset + 0x19,sf);
+                track_count = read_s16(offset + 0x19, sf);
                 /* 1a(1): flags 2 */
                 /* 1b(5): unknown 2 */
 
@@ -280,15 +425,15 @@ static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STR
                 offset += 0x20;
 
                 for (t = 0; t < track_count; t++) {
-                    stream_index    = read_s16(offset + 0x00,sf);
-                    wavebank_index  = read_s8 (offset + 0x02,sf);
+                    stream_index    = read_s16(offset + 0x00, sf);
+                    wavebank_index  = read_s8 (offset + 0x02, sf);
                     /* 03(1): min weight */
                     /* 04(1): min weight */
 
                     //;VGM_LOG("XSB clip event 6: track=%i, stream=%i, wavebank=%i at %lx\n", t, stream_index, wavebank_index, offset);
                     offset += 0x05;
 
-                    xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+                    xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
                     if (xsb->parse_done) return 1;
                 }
                 break;
@@ -318,7 +463,7 @@ fail:
     return 0;
 }
 
-static int parse_xsb_sound(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
+static int parse_xsb_sound(xsb_header *xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
     int32_t  (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
     int16_t  (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
 
@@ -327,7 +472,7 @@ static int parse_xsb_sound(xsb_header * xsb, off_t offset, off_t name_offset, ST
     int i, clip_count = 0;
 
 
-    flags = read_u8 (offset + 0x00,sf);
+    flags = read_u8 (offset + 0x00, sf);
     /* 0x01(2): category */
     /* 0x03(1): decibels */
     /* 0x04(2): pitch */
@@ -338,24 +483,24 @@ static int parse_xsb_sound(xsb_header * xsb, off_t offset, off_t name_offset, ST
     offset += 0x09;
 
     if (flags & 0x01) { /* complex sound */
-        clip_count      = read_u8 (offset + 0x00,sf);
+        clip_count      = read_u8 (offset + 0x00, sf);
 
         //;VGM_LOG("XSB sound: complex with clips=%i\n", clip_count);
         offset += 0x01;
     }
     else {
-        stream_index    = read_s16(offset + 0x00,sf);
-        wavebank_index  =  read_s8(offset + 0x02,sf);
+        stream_index    = read_s16(offset + 0x00, sf);
+        wavebank_index  =  read_s8(offset + 0x02, sf);
 
         //;VGM_LOG("XSB sound: simple with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
         offset += 0x03;
 
-        xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+        xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
         if (xsb->parse_done) return 1;
     }
 
     if (flags & 0x0E) { /* has RPCs */
-        size_t rpc_size = read_s16(offset + 0x00,sf);
+        size_t rpc_size = read_s16(offset + 0x00, sf);
         /* 0x02(2): preset count */
         /* 0x04(4*count): RPC indexes */
         /* (presets per flag 2/4/8 flag) */
@@ -363,7 +508,7 @@ static int parse_xsb_sound(xsb_header * xsb, off_t offset, off_t name_offset, ST
     }
 
     if (flags & 0x10) { /* has DSPs */
-        size_t dsp_size = read_s16(offset + 0x00,sf);
+        size_t dsp_size = read_s16(offset + 0x00, sf);
         /* follows RPC format? */
         offset += dsp_size;
     }
@@ -372,14 +517,14 @@ static int parse_xsb_sound(xsb_header * xsb, off_t offset, off_t name_offset, ST
         off_t clip_offset;
         for (i = 0; i < clip_count; i++) {
             /* 00(1): decibels */
-            clip_offset = read_s32(offset + 0x01,sf);
+            clip_offset = read_s32(offset + 0x01, sf);
             /* 05(2): filter config */
             /* 07(2): filter frequency */
 
             //;VGM_LOG("XSB sound clip %i at %lx\n", i, offset);
             offset += 0x09;
 
-            parse_xsb_clip(xsb, clip_offset, name_offset,sf);
+            parse_xsb_clip(xsb, clip_offset, name_offset, sf);
             if (xsb->parse_done) return 1;
         }
     }
@@ -387,7 +532,7 @@ static int parse_xsb_sound(xsb_header * xsb, off_t offset, off_t name_offset, ST
     return 0;
 }
 
-static int parse_xsb_variation(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
+static int parse_xsb_variation(xsb_header *xsb, off_t offset, off_t name_offset, STREAMFILE *sf) {
     int32_t  (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
     uint16_t (*read_u16)(off_t,STREAMFILE*) = xsb->big_endian ? read_u16be : read_u16le;
     int16_t  (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le;
@@ -397,8 +542,8 @@ static int parse_xsb_variation(xsb_header * xsb, off_t offset, off_t name_offset
     int i, variation_count;
 
 
-    variation_count = read_s16(offset + 0x00,sf);
-    flags           = read_u16(offset + 0x02,sf);
+    variation_count = read_s16(offset + 0x00, sf);
+    flags           = read_u16(offset + 0x02, sf);
 
     //;VGM_LOG("XSB variation at %lx\n", offset);
     offset += 0x04;
@@ -408,32 +553,32 @@ static int parse_xsb_variation(xsb_header * xsb, off_t offset, off_t name_offset
 
         switch ((flags >> 3) & 0x7) {
             case 0: /* wave */
-                stream_index   = read_s16(offset + 0x00,sf);
-                wavebank_index =  read_s8(offset + 0x02,sf);
+                stream_index   = read_s16(offset + 0x00, sf);
+                wavebank_index =  read_s8(offset + 0x02, sf);
                 /* 03(1): weight min */
                 /* 04(1): weight max */
 
                 //;VGM_LOG("XSB variation: type 0 with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
                 offset += 0x05;
 
-                xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+                xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
                 if (xsb->parse_done) return 1;
                 break;
 
             case 1: /* sound */
-                sound_offset = read_s32(offset + 0x00,sf);
+                sound_offset = read_s32(offset + 0x00, sf);
                 /* 04(1): weight min */
                 /* 05(1): weight max */
 
                 //;VGM_LOG("XSB variation: type 1\n");
                 offset += 0x06;
 
-                parse_xsb_sound(xsb, sound_offset, name_offset,sf);
+                parse_xsb_sound(xsb, sound_offset, name_offset, sf);
                 if (xsb->parse_done) return 1;
                 break;
 
             case 3: /* sound */
-                sound_offset = read_s32(offset + 0x00,sf);
+                sound_offset = read_s32(offset + 0x00, sf);
                 /* 04(4): weight min */
                 /* 08(4): weight max */
                 /* 0c(4): flags */
@@ -441,18 +586,18 @@ static int parse_xsb_variation(xsb_header * xsb, off_t offset, off_t name_offset
                 //;VGM_LOG("XSB variation: type 3\n");
                 offset += 0x10;
 
-                parse_xsb_sound(xsb, sound_offset, name_offset,sf);
+                parse_xsb_sound(xsb, sound_offset, name_offset, sf);
                 if (xsb->parse_done) return 1;
                 break;
 
             case 4: /* compact wave */
-                stream_index   = read_s16(offset + 0x00,sf);
-                wavebank_index =  read_s8(offset + 0x02,sf);
+                stream_index   = read_s16(offset + 0x00, sf);
+                wavebank_index =  read_s8(offset + 0x02, sf);
 
                 //;VGM_LOG("XSB variation: type 4 with stream=%i, wavebank=%i\n", stream_index, wavebank_index);
                 offset += 0x03;
 
-                xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf);
+                xsb_check_stream(xsb, stream_index, wavebank_index, name_offset, sf);
                 if (xsb->parse_done) return 1;
                 break;
 
@@ -474,7 +619,7 @@ fail:
 }
 
 
-static int parse_xsb_cues_new(xsb_header * xsb, STREAMFILE *sf) {
+static int parse_xsb_cues(xsb_header *xsb, STREAMFILE *sf) {
     int32_t  (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le;
 
     uint8_t flags;
@@ -486,23 +631,23 @@ static int parse_xsb_cues_new(xsb_header * xsb, STREAMFILE *sf) {
     offset = xsb->simple_cues_offset;
     for (i = 0; i < xsb->simple_cues_count; i++) {
         /* 00(1): flags */
-        sound_offset = read_s32(offset + 0x01,sf);
+        sound_offset = read_s32(offset + 0x01, sf);
 
         //;VGM_LOG("XSB cues: simple %i at %lx\n", i, offset);
         offset += 0x05;
 
-        name_offset = read_s32(names_offset + 0x00,sf);
+        name_offset = read_s32(names_offset + 0x00, sf);
         /* 04(2): unknown (-1) */
         names_offset += 0x06;
 
-        parse_xsb_sound(xsb, sound_offset, name_offset,sf);
+        parse_xsb_sound(xsb, sound_offset, name_offset, sf);
         if (xsb->parse_done) break;
     }
 
     offset = xsb->complex_cues_offset;
     for (i = 0; i < xsb->complex_cues_count; i++) {
-        flags = read_u8(offset + 0x00,sf);
-        sound_offset = read_s32(offset + 0x01,sf);
+        flags = read_u8(offset + 0x00, sf);
+        sound_offset = read_s32(offset + 0x01, sf);
         /* 05(4): unknown (sound) / transition table offset (variation) */
         /* 09(1): instance limit */
         /* 0a(2): fade in sec */
@@ -512,14 +657,14 @@ static int parse_xsb_cues_new(xsb_header * xsb, STREAMFILE *sf) {
         //;VGM_LOG("XSB cues: complex %i at %lx\n", i, offset);
         offset += 0x0f;
 
-        name_offset = read_s32(names_offset + 0x00,sf);
+        name_offset = read_s32(names_offset + 0x00, sf);
         /* 04(2): unknown (-1) */
         names_offset += 0x06;
 
         if (flags & (1<<2))
-            parse_xsb_sound(xsb, sound_offset, name_offset,sf);
+            parse_xsb_sound(xsb, sound_offset, name_offset, sf);
         else
-            parse_xsb_variation(xsb, sound_offset, name_offset,sf);
+            parse_xsb_variation(xsb, sound_offset, name_offset, sf);
         if (xsb->parse_done) break;
     }
 
@@ -547,7 +692,7 @@ static int parse_xsb_cues_new(xsb_header * xsb, STREAMFILE *sf) {
  * - https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Audio/Xact/
  * - https://github.com/espes/MacTerrariaWrapper/tree/master/xactxtract
  */
-static int parse_xsb(xsb_header * xsb, STREAMFILE *sf, char *xwb_wavebank_name) {
+static int parse_xsb(xsb_header *xsb, STREAMFILE *sf, char *xwb_wavebank_name) {
     int32_t  (*read_s32)(off_t,STREAMFILE*) = NULL;
     int16_t  (*read_s16)(off_t,STREAMFILE*) = NULL;
 
@@ -563,77 +708,84 @@ static int parse_xsb(xsb_header * xsb, STREAMFILE *sf, char *xwb_wavebank_name)
 
 
     /* parse sound bank header */
-    xsb->version = read_s16(0x04,sf); /* tool version */
+    xsb->version = read_s16(0x04, sf); /* tool version */
     if (xsb->version <= XSB_XACT1_0_MAX) {
         /* 06(2): crc */
-        xsb->wavebanks_offset       = read_s32(0x08,sf);
+        xsb->wavebanks_offset       = read_s32(0x08, sf);
         /* 0c(4): unknown1 offset (entry: 0x04) */
         /* 10(4): unknown2 offset */
         /* 14(2): element count? */
         /* 16(2): empty? */
         /* 18(2): empty? */
-        xsb->complex_cues_count     = read_s16(0x1a,sf);
-        xsb->simple_cues_count      = read_s16(0x1c,sf);
-        xsb->wavebanks_count        = read_s16(0x1e,sf);
+        xsb->complex_cues_count     = read_s16(0x1a, sf);
+        xsb->simple_cues_count      = read_s16(0x1c, sf);
+        xsb->wavebanks_count        = read_s16(0x1e, sf);
         /* 20(10): xsb name */
 
         xsb->sounds_offset          = 0x30;
         xsb->wavebanks_name_size    = 0x10;
+        xsb->index_size             = 0x10;
+        xsb->entry_size             = 0x14;
+
     }
     else if (xsb->version <= XSB_XACT1_1_MAX) {
         /* 06(2): crc */
-        xsb->wavebanks_offset       = read_s32(0x08,sf);
+        xsb->wavebanks_offset       = read_s32(0x08, sf);
         /* 0c(4): unknown1 offset (entry: 0x04) */
         /* 10(4): unknown2 offset */
         /* 14(4): unknown3 offset */
         /* 18(2): empty? */
         /* 1a(2): element count? */
-        xsb->complex_cues_count     = read_s16(0x1c,sf);
-        xsb->simple_cues_count      = read_s16(0x1e,sf);
+        xsb->complex_cues_count     = read_s16(0x1c, sf);
+        xsb->simple_cues_count      = read_s16(0x1e, sf);
         /* 20(2): unknown count? (related to unknown2?) */
-        xsb->wavebanks_count        = read_s16(0x22,sf);
+        xsb->wavebanks_count        = read_s16(0x22, sf);
         /* 24(10): xsb name */
 
         xsb->sounds_offset          = 0x34;
         xsb->wavebanks_name_size    = 0x10;
+        xsb->index_size             = 0x10;
+        xsb->entry_size             = 0x14;
     }
     else if (xsb->version <= XSB_XACT1_2_MAX) {
         /* 06(2): crc */
-        xsb->wavebanks_offset       = read_s32(0x08,sf);
+        xsb->wavebanks_offset       = read_s32(0x08, sf);
         /* 0c(4): unknown1 offset (entry: 0x14) */
         /* 10(4): unknown2 offset (entry: variable) */
         /* 14(4): unknown3 offset */
         /* 18(2): empty? */
         /* 1a(2): element count? */
-        xsb->complex_cues_count     = read_s16(0x1c,sf);
-        xsb->simple_cues_count      = read_s16(0x1e,sf);
+        xsb->complex_cues_count     = read_s16(0x1c, sf);
+        xsb->simple_cues_count      = read_s16(0x1e, sf);
         /* 20(2): unknown count? (related to unknown2?) */
-        xsb->wavebanks_count        = read_s16(0x22,sf);
+        xsb->wavebanks_count        = read_s16(0x22, sf);
         /* 24(4): null? */
         /* 28(10): xsb name */
 
         xsb->sounds_offset          = 0x38;
         xsb->wavebanks_name_size    = 0x10;
+        xsb->index_size             = 0x14;
+        xsb->entry_size             = 0x14;
     }
     else if (xsb->version <= XSB_XACT2_MAX) {
         /* 06(2): crc */
         /* 08(1): platform? (3=X360) */
-        xsb->simple_cues_count      = read_s16(0x09,sf);
-        xsb->complex_cues_count     = read_s16(0x0B,sf);
-        xsb->wavebanks_count        = read_s8 (0x11,sf);
-        xsb->sounds_count           = read_s16(0x12,sf);
+        xsb->simple_cues_count      = read_s16(0x09, sf);
+        xsb->complex_cues_count     = read_s16(0x0B, sf);
+        xsb->wavebanks_count        = read_s8 (0x11, sf);
+        xsb->sounds_count           = read_s16(0x12, sf);
         /* 14(2): unknown */
-        xsb->cue_names_size         = read_s32(0x16,sf);
-        xsb->simple_cues_offset     = read_s32(0x1a,sf);
-        xsb->complex_cues_offset    = read_s32(0x1e,sf);
-        xsb->cue_names_offset       = read_s32(0x22,sf);
+        xsb->cue_names_size         = read_s32(0x16, sf);
+        xsb->simple_cues_offset     = read_s32(0x1a, sf);
+        xsb->complex_cues_offset    = read_s32(0x1e, sf);
+        xsb->cue_names_offset       = read_s32(0x22, sf);
         /* 26(4): unknown */
         /* 2a(4): unknown */
         /* 2e(4): unknown */
-        xsb->wavebanks_offset       = read_s32(0x32,sf);
+        xsb->wavebanks_offset       = read_s32(0x32, sf);
         /* 36(4): cue name hash table offset? */
-        xsb->nameoffsets_offset     = read_s32(0x3a,sf);
-        xsb->sounds_offset          = read_s32(0x3e,sf);
+        xsb->nameoffsets_offset     = read_s32(0x3a, sf);
+        xsb->sounds_offset          = read_s32(0x3e, sf);
         /* 42(4): unknown */
         /* 46(4): unknown */
         /* 4a(64): xsb name */
@@ -646,23 +798,23 @@ static int parse_xsb(xsb_header * xsb, STREAMFILE *sf, char *xwb_wavebank_name)
         /* 0a(4): last modified low */
         /* 0e(4): last modified high */
         /* 12(1): platform? (1=PC, 3=X360) */
-        xsb->simple_cues_count      = read_s16(0x13,sf);
-        xsb->complex_cues_count     = read_s16(0x15,sf);
+        xsb->simple_cues_count      = read_s16(0x13, sf);
+        xsb->complex_cues_count     = read_s16(0x15, sf);
         /* 17(2): unknown count? */
         /* 19(2): element count? (often simple+complex cues, but may be more) */
-        xsb->wavebanks_count        = read_s8 (0x1b,sf);
-        xsb->sounds_count           = read_s16(0x1c,sf);
-        xsb->cue_names_size         = read_s32(0x1e,sf);
-        xsb->simple_cues_offset     = read_s32(0x22,sf);
-        xsb->complex_cues_offset    = read_s32(0x26,sf);
-        xsb->cue_names_offset       = read_s32(0x2a,sf);
+        xsb->wavebanks_count        = read_s8 (0x1b, sf);
+        xsb->sounds_count           = read_s16(0x1c, sf);
+        xsb->cue_names_size         = read_s32(0x1e, sf);
+        xsb->simple_cues_offset     = read_s32(0x22, sf);
+        xsb->complex_cues_offset    = read_s32(0x26, sf);
+        xsb->cue_names_offset       = read_s32(0x2a, sf);
         /* 0x2E(4): unknown offset */
         /* 0x32(4): variation tables offset */
         /* 0x36(4): unknown offset */
-        xsb->wavebanks_offset       = read_s32(0x3a,sf);
+        xsb->wavebanks_offset       = read_s32(0x3a, sf);
         /* 0x3E(4): cue name hash table offset (16b each) */
-        xsb->nameoffsets_offset     = read_s32(0x42,sf);
-        xsb->sounds_offset          = read_s32(0x46,sf);
+        xsb->nameoffsets_offset     = read_s32(0x42, sf);
+        xsb->sounds_offset          = read_s32(0x46, sf);
         /* 4a(64): xsb name */
 
         xsb->wavebanks_name_size    = 0x40;
@@ -692,7 +844,7 @@ static int parse_xsb(xsb_header * xsb, STREAMFILE *sf, char *xwb_wavebank_name)
 
         offset = xsb->wavebanks_offset;
         for (i = 0; i < xsb->wavebanks_count; i++) {
-            read_string(xsb_wavebank_name,xsb->wavebanks_name_size, offset,sf);
+            read_string(xsb_wavebank_name,xsb->wavebanks_name_size, offset, sf);
             //;VGM_LOG("XSB wavebanks: bank %i=%s\n", i, wavebank_name);
             if (strcasecmp(xsb_wavebank_name, xwb_wavebank_name)==0) {
                 //;VGM_LOG("XSB banks: current xwb is wavebank %i=%s\n", i, xsb_wavebank_name);
@@ -705,17 +857,17 @@ static int parse_xsb(xsb_header * xsb, STREAMFILE *sf, char *xwb_wavebank_name)
         //;VGM_LOG("xsb: selected wavebank=%i\n", xsb->selected_wavebank);
         if (xsb->selected_wavebank == -1) {
             VGM_LOG("XSB: current wavebank not found, selecting first\n");
-            xsb->selected_wavebank = 0; //todo goto fail?
+            xsb->selected_wavebank = 0;
         }
     }
 
 
     /* find cue pointing to stream */
     if (xsb->version <= XSB_XACT1_2_MAX) {
-        parse_xsb_cues_old(xsb, sf);
+        parse_xsb_old_cues(xsb, sf);
     }
     else {
-        parse_xsb_cues_new(xsb, sf);
+        parse_xsb_cues(xsb, sf);
     }
 
     return 1;
@@ -739,6 +891,12 @@ static STREAMFILE * open_xsb_filename_pair(STREAMFILE *streamXwb) {
             {"StreamBank_*.xwb","SoundBank_*.xsb"},     /* Ginga Force (X360) */
             {"WaveBank_*.xwb","SoundBank_*.xsb"},       /* Ginga Force (X360) */
             {"*_WB.xwb","*_SB.xsb"},                    /* Ninja Blade (X360) */
+            {"*_WB.xwb","*_SB.xsb"},                    /* Ninja Blade (X360) */
+            {"CA_NightMusic.xwb","CAMusic.xsb"},        /* Psychonauts (Xbox) */
+            {"CAJAMusic.xwb","CAMusic.xsb"},            /* "" */
+            {"STFX.xwb","CommonMusic.xsb"},             /* "" */
+            {"CALI_NightFX.xwb","CAFX.xsb"},            /* "" */
+            /* Psychonauts has a bunch more pairs for sfx too, improve */
             {"*.xwb","*.xsb"},                          /* default */
     };
     int i;
diff --git a/Frameworks/vgmstream/vgmstream/src/streamfile.c b/Frameworks/vgmstream/vgmstream/src/streamfile.c
index 6323e5276..b9b90d50d 100644
--- a/Frameworks/vgmstream/vgmstream/src/streamfile.c
+++ b/Frameworks/vgmstream/vgmstream/src/streamfile.c
@@ -29,6 +29,8 @@ static size_t read_stdio(STDIO_STREAMFILE *streamfile, uint8_t *dst, off_t offse
     if (!streamfile->infile || !dst || length <= 0 || offset < 0)
         return 0;
 
+    //;VGM_LOG("STDIO: read %lx + %x (buf %lx + %x)\n", offset, length, streamfile->buffer_offset, streamfile->validsize);
+
     /* is the part of the requested length in the buffer? */
     if (offset >= streamfile->buffer_offset && offset < streamfile->buffer_offset + streamfile->validsize) {
         size_t length_to_read;
@@ -38,6 +40,8 @@ static size_t read_stdio(STDIO_STREAMFILE *streamfile, uint8_t *dst, off_t offse
         if (length_to_read > length)
             length_to_read = length;
 
+        //;VGM_LOG("STDIO: copy buf %lx + %x (+ %x) (buf %lx + %x)\n", offset, length_to_read, (length - length_to_read), streamfile->buffer_offset, streamfile->validsize);
+
         memcpy(dst, streamfile->buffer + offset_into_buffer, length_to_read);
         length_read_total += length_to_read;
         length -= length_to_read;
@@ -46,8 +50,10 @@ static size_t read_stdio(STDIO_STREAMFILE *streamfile, uint8_t *dst, off_t offse
     }
 
 #ifdef VGM_DEBUG_OUTPUT
-    if (offset < streamfile->buffer_offset) {
+    if (offset < streamfile->buffer_offset && length > 0) {
         VGM_LOG("STDIO: rebuffer, requested %lx vs %lx (sf %x)\n", offset, streamfile->buffer_offset, (uint32_t)streamfile);
+        //streamfile->rebuffer++;
+        //if (rebuffer > N) ...
     }
 #endif
 
@@ -78,6 +84,7 @@ static size_t read_stdio(STDIO_STREAMFILE *streamfile, uint8_t *dst, off_t offse
         /* fill the buffer (offset now is beyond buffer_offset) */
         streamfile->buffer_offset = offset;
         streamfile->validsize = fread(streamfile->buffer, sizeof(uint8_t), streamfile->buffersize, streamfile->infile);
+        //;VGM_LOG("STDIO: read buf %lx + %x\n", streamfile->buffer_offset, streamfile->validsize);
 
         /* decide how much must be read this time */
         if (length > streamfile->buffersize)
@@ -126,20 +133,21 @@ static STREAMFILE* open_stdio(STDIO_STREAMFILE *streamfile, const char * const f
         return NULL;
 
 #if !defined (__ANDROID__)
-    // if same name, duplicate the file pointer we already have open
+    /* if same name, duplicate the file descriptor we already have open */
     if (streamfile->infile && !strcmp(streamfile->name,filename)) {
-        int newfd;
-        FILE *newfile;
-        STREAMFILE *new_sf;
+        int new_fd;
+        FILE *new_file = NULL;
 
-        if ( ((newfd = dup(fileno(streamfile->infile))) >= 0) && (newfile = fdopen(newfd, "rb")) )  {
-            new_sf = open_stdio_streamfile_buffer_by_file(newfile, filename, buffersize);
-            if (new_sf) {
+        if (((new_fd = dup(fileno(streamfile->infile))) >= 0) && (new_file = fdopen(new_fd, "rb")))  {
+            STREAMFILE *new_sf = open_stdio_streamfile_buffer_by_file(new_file, filename, buffersize);
+            if (new_sf)
                 return new_sf;
-            }
-            // failure, close it and try the default path (which will probably fail a second time)
-            fclose(newfile);
+            fclose(new_file);
         }
+        if (new_fd >= 0 && !new_file)
+            close(new_fd); /* fdopen may fail when opening too many files */
+
+        /* on failure just close and try the default path (which will probably fail a second time) */
     }
 #endif    
     // a normal open, open a new file
@@ -981,6 +989,27 @@ fail:
     if (buf) buf[0] = '\0';
     return 0;
 }
+size_t read_string_utf16le(char *buf, size_t buf_size, off_t offset, STREAMFILE *sf) {
+    size_t pos, offpos;
+
+    for (pos = 0, offpos = 0; pos < buf_size; pos++, offpos += 2) {
+        char c = read_u16le(offset + offpos, sf) & 0xFF; /* lower byte for now */
+        if (buf) buf[pos] = c;
+        if (c == '\0')
+            return pos;
+        if (pos+1 == buf_size) { /* null at maxsize and don't validate (expected to be garbage) */
+            if (buf) buf[pos] = '\0';
+            return buf_size;
+        }
+        if (c < 0x20 || (uint8_t)c > 0xA5)
+            goto fail;
+    }
+
+fail:
+    if (buf) buf[0] = '\0';
+    return 0;
+}
+
 
 
 size_t read_key_file(uint8_t *buf, size_t buf_size, STREAMFILE *sf) {
diff --git a/Frameworks/vgmstream/vgmstream/src/streamfile.h b/Frameworks/vgmstream/vgmstream/src/streamfile.h
index e66564252..0e184fec2 100644
--- a/Frameworks/vgmstream/vgmstream/src/streamfile.h
+++ b/Frameworks/vgmstream/vgmstream/src/streamfile.h
@@ -45,8 +45,6 @@
 #define fseeko fseek
 #endif
 
-#define STREAMFILE_DEFAULT_BUFFER_SIZE 0x8000
-
 #ifndef DIR_SEPARATOR
 #if defined (_WIN32) || defined (WIN32)
 #define DIR_SEPARATOR '\\'
@@ -55,6 +53,14 @@
 #endif
 #endif
 
+/* Streamfiles normally use an internal buffer to increase performance, configurable
+ * but usually of this size. Lower increases the number of freads/system calls (slower).
+ * However some formats need to jump around causing more buffer trashing than usual,
+ * higher may needlessly read data that may be going to be trashed.
+ *
+ * Value can be adjusted freely but 8k is a good enough compromise. */
+#define STREAMFILE_DEFAULT_BUFFER_SIZE 0x8000
+
 /* struct representing a file with callbacks. Code should use STREAMFILEs and not std C functions
  * to do file operations, as plugins may need to provide their own callbacks.
  * Reads from arbitrary offsets, meaning internally may need fseek equivalents during reads. */
@@ -202,30 +208,81 @@ static inline int8_t read_8bit(off_t offset, STREAMFILE * streamfile) {
 }
 
 /* alias of the above */
-static inline int8_t   read_s8(off_t offset, STREAMFILE * streamfile) { return read_8bit(offset, streamfile); }
-static inline uint8_t  read_u8(off_t offset, STREAMFILE * streamfile) { return (uint8_t)read_8bit(offset, streamfile); }
-static inline int16_t  read_s16le(off_t offset, STREAMFILE * streamfile) { return read_16bitLE(offset, streamfile); }
-static inline uint16_t read_u16le(off_t offset, STREAMFILE * streamfile) { return (uint16_t)read_16bitLE(offset, streamfile); }
-static inline int16_t  read_s16be(off_t offset, STREAMFILE * streamfile) { return read_16bitBE(offset, streamfile); }
-static inline uint16_t read_u16be(off_t offset, STREAMFILE * streamfile) { return (uint16_t)read_16bitBE(offset, streamfile); }
-static inline int32_t  read_s32le(off_t offset, STREAMFILE * streamfile) { return read_32bitLE(offset, streamfile); }
-static inline uint32_t read_u32le(off_t offset, STREAMFILE * streamfile) { return (uint32_t)read_32bitLE(offset, streamfile); }
-static inline int32_t  read_s32be(off_t offset, STREAMFILE * streamfile) { return read_32bitBE(offset, streamfile); }
-static inline uint32_t read_u32be(off_t offset, STREAMFILE * streamfile) { return (uint32_t)read_32bitBE(offset, streamfile); }
-static inline int64_t  read_s64be(off_t offset, STREAMFILE * streamfile) { return read_64bitBE(offset, streamfile); }
-static inline uint64_t read_u64be(off_t offset, STREAMFILE * streamfile) { return (uint64_t)read_64bitBE(offset, streamfile); }
-static inline int64_t  read_s64le(off_t offset, STREAMFILE * streamfile) { return read_64bitLE(offset, streamfile); }
-static inline uint64_t read_u64le(off_t offset, STREAMFILE * streamfile) { return (uint64_t)read_64bitLE(offset, streamfile); }
+static inline int8_t   read_s8   (off_t offset, STREAMFILE *sf) { return           read_8bit(offset, sf); }
+static inline uint8_t  read_u8   (off_t offset, STREAMFILE *sf) { return (uint8_t) read_8bit(offset, sf); }
+static inline int16_t  read_s16le(off_t offset, STREAMFILE *sf) { return           read_16bitLE(offset, sf); }
+static inline uint16_t read_u16le(off_t offset, STREAMFILE *sf) { return (uint16_t)read_16bitLE(offset, sf); }
+static inline int16_t  read_s16be(off_t offset, STREAMFILE *sf) { return           read_16bitBE(offset, sf); }
+static inline uint16_t read_u16be(off_t offset, STREAMFILE *sf) { return (uint16_t)read_16bitBE(offset, sf); }
+static inline int32_t  read_s32le(off_t offset, STREAMFILE *sf) { return           read_32bitLE(offset, sf); }
+static inline uint32_t read_u32le(off_t offset, STREAMFILE *sf) { return (uint32_t)read_32bitLE(offset, sf); }
+static inline int32_t  read_s32be(off_t offset, STREAMFILE *sf) { return           read_32bitBE(offset, sf); }
+static inline uint32_t read_u32be(off_t offset, STREAMFILE *sf) { return (uint32_t)read_32bitBE(offset, sf); }
+static inline int64_t  read_s64be(off_t offset, STREAMFILE *sf) { return           read_64bitBE(offset, sf); }
+static inline uint64_t read_u64be(off_t offset, STREAMFILE *sf) { return (uint64_t)read_64bitBE(offset, sf); }
+static inline int64_t  read_s64le(off_t offset, STREAMFILE *sf) { return           read_64bitLE(offset, sf); }
+static inline uint64_t read_u64le(off_t offset, STREAMFILE *sf) { return (uint64_t)read_64bitLE(offset, sf); }
 
-#if 0  //todo improve + test + simplify code (maybe not inline?)
-static inline float    read_f32be(off_t offset, STREAMFILE * streamfile) {
-    uint32_t sample_int = read_s32be(offset,streamfile);
+/* The recommended int-to-float type punning in C is through union, but pointer casting
+ * works too (though less portable due to aliasing rules?). For C++ memcpy seems
+ * recommended. Both work in GCC and VS2015+ (not sure about older, ifdef as needed). */
+static inline float    read_f32be(off_t offset, STREAMFILE *sf) {
+    union {
+        uint32_t u32;
+        float f32;
+    } temp;
+    temp.u32 = read_u32be(offset, sf);
+    return temp.f32;
+}
+static inline float    read_f32le(off_t offset, STREAMFILE * sf) {
+    union {
+        uint32_t u32;
+        float f32;
+    } temp;
+    temp.u32 = read_u32le(offset, sf);
+    return temp.f32;
+}
+#if 0
+static inline float    read_f32be_p(off_t offset, STREAMFILE *sf) {
+    uint32_t sample_int = read_u32be(offset, sf);
     float* sample_float = (float*)&sample_int;
     return *sample_float;
 }
-static inline float    read_f32le(off_t offset, STREAMFILE * streamfile) {
-    ...
+static inline float    read_f32be_m(off_t offset, STREAMFILE *sf) {
+    uint32_t sample_int = read_u32be(offset, sf);
+    float sample_float;
+    memcpy(&sample_float, &sample_int, sizeof(uint32_t));
+    return sample_float;
 }
+#endif
+#if 0
+/* collection of callbacks for quick access */
+typedef struct sf_reader {
+    int32_t (*read_s32)(off_t,STREAMFILE*); //maybe s32
+    float (*read_f32)(off_t,STREAMFILE*);
+    /* ... */
+} sf_reader;
+
+void init_reader(sf_reader *r, int big_endian);
+/* ... */
+void sf_reader_init(sf_reader *r, int big_endian) {
+    memset(r, 0, sizeof(sf_reader));
+    if (big_endian) {
+        r->read_s32 = read_s32be;
+        r->read_f32 = read_f32be;
+    }
+    else {
+        r->read_s32 = read_s32le;
+        r->read_f32 = read_f32le;
+    }
+}
+/* sf_reader r;
+ * ...
+ * sf_reader_init(&r, big_endian);
+ * val = r.read_s32; //maybe r.s32?
+ */
+#endif
+#if 0  //todo improve + test + simplify code (maybe not inline?)
 static inline int read_s4h(off_t offset, STREAMFILE * streamfile) {
     uint8_t byte = read_u8(offset, streamfile);
     return get_nibble_signed(byte, 1);
@@ -245,6 +302,7 @@ static inline int min_s32(int32_t a, int32_t b) { return a < b ? a : b; }
 //align32, align16, clamp16, etc
 #endif
 
+//TODO: maybe move to streamfile.c
 /* guess byte endianness from a given value, return true if big endian and false if little endian */
 static inline int guess_endianness16bit(off_t offset, STREAMFILE * streamfile) {
     uint8_t buf[0x02];
@@ -272,6 +330,8 @@ size_t read_line(char *buf, int buf_size, off_t offset, STREAMFILE *sf, int *p_l
 
 /* reads a c-string (ANSI only), up to bufsize or NULL, returning size. buf is optional (works as get_string_size). */
 size_t read_string(char *buf, size_t buf_size, off_t offset, STREAMFILE *sf);
+/* reads a UTF16 string... but actually only as ANSI (discards the upper byte) */
+size_t read_string_utf16le(char *buf, size_t buf_size, off_t offset, STREAMFILE *sf);
 
 /* Opens a file containing decryption keys and copies to buffer.
  * Tries "(name.ext)key" (per song), "(.ext)key" (per folder) keynames.
diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c
index 4bec02e99..8d27fb097 100644
--- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c
+++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c
@@ -51,6 +51,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
     init_vgmstream_ps2_mic,
     init_vgmstream_ngc_dsp_std_int,
     init_vgmstream_vag,
+    init_vgmstream_vag_aaap,
     init_vgmstream_seb,
     init_vgmstream_ps2_ild,
     init_vgmstream_ps2_pnb,
@@ -221,7 +222,6 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
     init_vgmstream_pona_3do,
     init_vgmstream_pona_psx,
     init_vgmstream_xbox_hlwav,
-    init_vgmstream_stx,
     init_vgmstream_myspd,
     init_vgmstream_his,
     init_vgmstream_ps2_ast,
@@ -481,6 +481,8 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
     init_vgmstream_bmp_konami,
     init_vgmstream_opus_opusnx,
     init_vgmstream_opus_sqex,
+    init_vgmstream_isb,
+    init_vgmstream_xssb,
 
     /* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
     init_vgmstream_txth,            /* proper parsers should supersede TXTH, once added */
@@ -2444,13 +2446,13 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
 static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *streamFile, VGMSTREAM*(*init_vgmstream_function)(STREAMFILE *)) {
     /* filename search pairs for dual file stereo */
     static const char * const dfs_pairs[][2] = {
-        {"L","R"},
-        {"l","r"},
-        {"left","right"},
-        {"Left","Right"},
+        {"L","R"}, /* most common in .dsp and .vag */
+        {"l","r"}, /* same */
+        {"left","right"}, /* Freaky Flyers (GC) .adp, Velocity (PSP) .vag, Hyper Fighters (Wii) .dsp */
+        {"Left","Right"}, /* Geometry Wars: Galaxies (Wii) .dsp */
         {".V0",".V1"}, /* Homura (PS2) */
         {".L",".R"}, /* Crash Nitro Racing (PS2), Gradius V (PS2) */
-        {"_0","_1"}, /* fake for Homura/unneeded? */
+        {"_0.dsp","_1.dsp"}, /* Wario World (GC) */
         {".adpcm","_NxEncoderOut_.adpcm"}, /* Kill la Kill: IF (Switch) */ //todo can't match R>L
     };
     char new_filename[PATH_LIMIT];
@@ -2470,7 +2472,7 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea
     //todo other layouts work but some stereo codecs do weird things
     //if (opened_vgmstream->layout != layout_none) return;
 
-    get_streamfile_name(streamFile,new_filename,sizeof(new_filename));
+    get_streamfile_name(streamFile, new_filename, sizeof(new_filename));
     filename_len = strlen(new_filename);
     if (filename_len < 2)
         return;
@@ -2493,15 +2495,15 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea
             //;VGM_LOG("DFS: l=%s, r=%s\n", this_suffix,that_suffix);
 
             /* if suffix matches paste opposite suffix (+ terminator) to extension pointer, thus to new_filename */
-            if (this_suffix[0] == '.' && extension_len == this_suffix_len) { /* same extension */
-                //;VGM_LOG("DFS: same ext %s vs %s len %i\n", extension, this_suffix, this_suffix_len);
-                if (memcmp(extension,this_suffix,this_suffix_len) == 0) {
+            if (filename_len > this_suffix_len && strchr(this_suffix, '.') != NULL) { /* same suffix with extension */
+                //;VGM_LOG("DFS: suf+ext %s vs %s len %i\n", new_filename, this_suffix, this_suffix_len);
+                if (memcmp(new_filename + (filename_len - this_suffix_len), this_suffix, this_suffix_len) == 0) {
                     dfs_pair = j;
-                    memcpy (extension, that_suffix,that_suffix_len+1);
+                    memcpy (new_filename + (filename_len - this_suffix_len), that_suffix,that_suffix_len+1);
                 }
             }
-            else if (filename_len - extension_len > this_suffix_len) { /* same suffix (without extension) */
-                //;VGM_LOG("DFS: same suf %s vs %s len %i\n", extension - this_suffix_len, this_suffix, this_suffix_len);
+            else if (filename_len - extension_len > this_suffix_len) { /* same suffix without extension */
+                //;VGM_LOG("DFS: suf-ext %s vs %s len %i\n", extension - this_suffix_len, this_suffix, this_suffix_len);
                 if (memcmp(extension - this_suffix_len, this_suffix,this_suffix_len) == 0) {
                     dfs_pair = j;
                     memmove(extension + that_suffix_len - this_suffix_len, extension,extension_len+1); /* move old extension to end */
@@ -2517,7 +2519,7 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea
     //;VGM_LOG("DFS: match %i filename=%s\n", dfs_pair, new_filename);
 
     /* try to init other channel (new_filename now has the opposite name) */
-    dual_streamFile = open_streamfile(streamFile,new_filename);
+    dual_streamFile = open_streamfile(streamFile, new_filename);
     if (!dual_streamFile) goto fail;
 
     new_vgmstream = init_vgmstream_function(dual_streamFile); /* use the init that just worked, no other should work */
@@ -2819,7 +2821,8 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s
     }
 
     /* if blocked layout (implicit) use multiple streamfiles; using only one leads to
-     * lots of buffer-trashing, with all the jumping around in the block layout */
+     * lots of buffer-trashing, with all the jumping around in the block layout
+     * (this increases total of data read but still seems faster) */
     if (vgmstream->layout_type != layout_none && vgmstream->layout_type != layout_interleave) {
         use_streamfile_per_channel = 1;
     }
@@ -2860,7 +2863,8 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s
                 offset = start_offset + vgmstream->interleave_block_size*ch;
             }
 
-            /* open new one if needed */
+            /* open new one if needed, useful to avoid jumping around when each channel data is too apart
+             * (don't use when data is close as it'd make buffers read the full file multiple times) */
             if (use_streamfile_per_channel) {
                 file = open_streamfile(streamFile,filename);
                 if (!file) goto fail;
diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.h b/Frameworks/vgmstream/vgmstream/src/vgmstream.h
index cb27e6189..488060900 100644
--- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h
+++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h
@@ -332,7 +332,6 @@ typedef enum {
     meta_NDS_SWAV,          /* Asphalt Urban GT 1 & 2 */
     meta_NDS_RRDS,          /* Ridge Racer DS */
     meta_WII_BNS,           /* Wii BNS Banner Sound (similar to RSTM) */
-    meta_STX,               /* Pikmin .stx */
     meta_WIIU_BTSND,        /* Wii U Boot Sound */
 
     meta_ADX_03,            /* CRI ADX "type 03" */
@@ -364,6 +363,7 @@ typedef enum {
     meta_PS2_VAGi,          /* VAGi Interleaved File */
     meta_PS2_VAGp,          /* VAGp Mono File */
     meta_PS2_pGAV,          /* VAGp with Little Endian Header */
+    meta_PS2_VAGp_AAAP,     /* Acclaim Austin Audio VAG header */
     meta_SEB,
     meta_STR_WAV,           /* Blitz Games STR+WAV files */
     meta_PS2_ILD,           /* ILD File */
@@ -726,6 +726,8 @@ typedef enum {
     meta_XMV_VALVE,
     meta_UBI_HX,
     meta_BMP_KONAMI,
+    meta_ISB,
+    meta_XSSB,
 
 } meta_t;
 
@@ -870,14 +872,14 @@ typedef struct {
     int32_t samples_into_block;     /* number of samples into the current block/interleave/segment/etc */
     off_t current_block_offset;     /* start of this block (offset of block header) */
     size_t current_block_size;      /* size in usable bytes of the block we're in now (used to calculate num_samples per block) */
-    size_t current_block_samples;   /* size in samples of the block we're in now (used over current_block_size if possible) */
+    int32_t current_block_samples;   /* size in samples of the block we're in now (used over current_block_size if possible) */
     off_t next_block_offset;        /* offset of header of the next block */
     /* layout/block loop state */
     int32_t loop_sample;            /* saved from current_sample (same as loop_start_sample, but more state-like) */
     int32_t loop_samples_into_block;/* saved from samples_into_block */
     off_t loop_block_offset;        /* saved from current_block_offset */
     size_t loop_block_size;         /* saved from current_block_size */
-    size_t loop_block_samples;      /* saved from current_block_samples */
+    int32_t loop_block_samples;      /* saved from current_block_samples */
     off_t loop_next_block_offset;   /* saved from next_block_offset */
 
     /* loop state */