From ebfb7e1207477acbc095c173bfdbd5d997a53696 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Sun, 7 Jun 2020 19:57:02 -0700 Subject: [PATCH] Updated VGMStream to r1050-3043-g295ffac0 --- .../libvgmstream.xcodeproj/project.pbxproj | 42 +- .../vgmstream/vgmstream/src/coding/coding.h | 1 + .../vgmstream/src/coding/coding_utils.c | 2 + .../vgmstream/src/coding/ffmpeg_decoder.c | 18 + .../vgmstream/src/coding/ima_decoder.c | 16 +- Frameworks/vgmstream/vgmstream/src/formats.c | 13 +- .../vgmstream/src/layout/blocked_ubi_sce.c | 12 +- Frameworks/vgmstream/vgmstream/src/meta/acb.c | 1311 +++++------ Frameworks/vgmstream/vgmstream/src/meta/aix.c | 26 +- .../vgmstream/vgmstream/src/meta/bnsf_keys.h | 11 +- .../vgmstream/src/meta/deblock_streamfile.c | 20 +- .../vgmstream/src/meta/deblock_streamfile.h | 6 +- .../vgmstream/vgmstream/src/meta/ea_eaac.c | 3 + .../vgmstream/vgmstream/src/meta/ea_schl.c | 10 +- .../vgmstream/vgmstream/src/meta/ffmpeg.c | 144 +- Frameworks/vgmstream/vgmstream/src/meta/fsb.c | 1206 +++++----- .../vgmstream/vgmstream/src/meta/fsb5.c | 126 +- .../vgmstream/vgmstream/src/meta/fsb5_fev.c | 128 +- .../vgmstream/src/meta/fsb_encrypted.c | 249 +-- .../src/meta/fsb_encrypted_streamfile.h | 73 + .../vgmstream/vgmstream/src/meta/hca_keys.h | 3 + Frameworks/vgmstream/vgmstream/src/meta/kat.c | 82 + .../vgmstream/vgmstream/src/meta/ktsr.c | 566 +++++ .../vgmstream/vgmstream/src/meta/meta.h | 12 +- .../vgmstream/vgmstream/src/meta/mups.c | 42 + .../vgmstream/src/meta/mups_streamfile.h | 102 + .../vgmstream/src/meta/ngc_dsp_std.c | 42 - .../vgmstream/src/meta/pcm_success.c | 68 + .../vgmstream/vgmstream/src/meta/ps2_joe.c | 4 +- .../vgmstream/vgmstream/src/meta/riff.c | 23 +- .../vgmstream/vgmstream/src/meta/sadf.c | 51 + .../vgmstream/src/meta/{nds_sad.c => sadl.c} | 34 +- .../vgmstream/vgmstream/src/meta/ubi_bao.c | 61 +- .../vgmstream/vgmstream/src/meta/ubi_sb.c | 1963 ++++++++++++----- .../vgmstream/vgmstream/src/meta/xvag.c | 100 +- .../vgmstream/vgmstream/src/streamfile.c | 10 +- Frameworks/vgmstream/vgmstream/src/util.c | 10 +- Frameworks/vgmstream/vgmstream/src/util.h | 12 +- .../vgmstream/vgmstream/src/vgmstream.c | 7 + .../vgmstream/vgmstream/src/vgmstream.h | 7 +- 40 files changed, 4297 insertions(+), 2319 deletions(-) create mode 100644 Frameworks/vgmstream/vgmstream/src/meta/fsb_encrypted_streamfile.h create mode 100644 Frameworks/vgmstream/vgmstream/src/meta/kat.c create mode 100644 Frameworks/vgmstream/vgmstream/src/meta/ktsr.c create mode 100644 Frameworks/vgmstream/vgmstream/src/meta/mups.c create mode 100644 Frameworks/vgmstream/vgmstream/src/meta/mups_streamfile.h create mode 100644 Frameworks/vgmstream/vgmstream/src/meta/pcm_success.c create mode 100644 Frameworks/vgmstream/vgmstream/src/meta/sadf.c rename Frameworks/vgmstream/vgmstream/src/meta/{nds_sad.c => sadl.c} (53%) diff --git a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj index 6c57c4d87..bd9b71b28 100644 --- a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj +++ b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj @@ -312,7 +312,6 @@ 836F6FA318BDC2190095E648 /* naomi_spsd.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E6718BDC2180095E648 /* naomi_spsd.c */; }; 836F6FA418BDC2190095E648 /* nds_hwas.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E6818BDC2180095E648 /* nds_hwas.c */; }; 836F6FA518BDC2190095E648 /* nds_rrds.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E6918BDC2180095E648 /* nds_rrds.c */; }; - 836F6FA618BDC2190095E648 /* nds_sad.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E6A18BDC2180095E648 /* nds_sad.c */; }; 836F6FA718BDC2190095E648 /* nds_strm.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E6B18BDC2180095E648 /* nds_strm.c */; }; 836F6FA818BDC2190095E648 /* nds_swav.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E6C18BDC2180095E648 /* nds_swav.c */; }; 836F6FA918BDC2190095E648 /* ngc_adpdtk.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E6D18BDC2180095E648 /* ngc_adpdtk.c */; }; @@ -579,6 +578,14 @@ 83C7282922BC8C1500678B4A /* mixing.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7282522BC8C1400678B4A /* mixing.c */; }; 83C7282A22BC8C1500678B4A /* plugins.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7282622BC8C1400678B4A /* plugins.c */; }; 83CD428A1F787879000F77BE /* libswresample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 83CD42851F787878000F77BE /* libswresample.a */; }; + 83D2007A248DDB770048BD24 /* fsb_encrypted_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 83D20072248DDB760048BD24 /* fsb_encrypted_streamfile.h */; }; + 83D2007B248DDB770048BD24 /* mups_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 83D20073248DDB760048BD24 /* mups_streamfile.h */; }; + 83D2007C248DDB770048BD24 /* ktsr.c in Sources */ = {isa = PBXBuildFile; fileRef = 83D20074248DDB760048BD24 /* ktsr.c */; }; + 83D2007D248DDB770048BD24 /* kat.c in Sources */ = {isa = PBXBuildFile; fileRef = 83D20075248DDB760048BD24 /* kat.c */; }; + 83D2007E248DDB770048BD24 /* pcm_success.c in Sources */ = {isa = PBXBuildFile; fileRef = 83D20076248DDB770048BD24 /* pcm_success.c */; }; + 83D2007F248DDB770048BD24 /* mups.c in Sources */ = {isa = PBXBuildFile; fileRef = 83D20077248DDB770048BD24 /* mups.c */; }; + 83D20080248DDB770048BD24 /* sadf.c in Sources */ = {isa = PBXBuildFile; fileRef = 83D20078248DDB770048BD24 /* sadf.c */; }; + 83D20081248DDB770048BD24 /* sadl.c in Sources */ = {isa = PBXBuildFile; fileRef = 83D20079248DDB770048BD24 /* sadl.c */; }; 83D2F58E2356B266007646ED /* libopus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 83D2F58A2356B266007646ED /* libopus.a */; }; 83D731891A749D1500CA1366 /* g719.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83D7313E1A74968A00CA1366 /* g719.framework */; }; 83D7318A1A749D2200CA1366 /* g719.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 83D7313E1A74968A00CA1366 /* g719.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -1019,7 +1026,6 @@ 836F6E6718BDC2180095E648 /* naomi_spsd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = naomi_spsd.c; sourceTree = ""; }; 836F6E6818BDC2180095E648 /* nds_hwas.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = nds_hwas.c; sourceTree = ""; }; 836F6E6918BDC2180095E648 /* nds_rrds.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = nds_rrds.c; sourceTree = ""; }; - 836F6E6A18BDC2180095E648 /* nds_sad.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = nds_sad.c; sourceTree = ""; }; 836F6E6B18BDC2180095E648 /* nds_strm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = nds_strm.c; sourceTree = ""; }; 836F6E6C18BDC2180095E648 /* nds_swav.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = nds_swav.c; sourceTree = ""; }; 836F6E6D18BDC2180095E648 /* ngc_adpdtk.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ngc_adpdtk.c; sourceTree = ""; }; @@ -1285,6 +1291,14 @@ 83C7282522BC8C1400678B4A /* mixing.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mixing.c; sourceTree = ""; }; 83C7282622BC8C1400678B4A /* plugins.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = plugins.c; sourceTree = ""; }; 83CD42851F787878000F77BE /* libswresample.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libswresample.a; path = ../../ThirdParty/ffmpeg/lib/libswresample.a; sourceTree = ""; }; + 83D20072248DDB760048BD24 /* fsb_encrypted_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fsb_encrypted_streamfile.h; sourceTree = ""; }; + 83D20073248DDB760048BD24 /* mups_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mups_streamfile.h; sourceTree = ""; }; + 83D20074248DDB760048BD24 /* ktsr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ktsr.c; sourceTree = ""; }; + 83D20075248DDB760048BD24 /* kat.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = kat.c; sourceTree = ""; }; + 83D20076248DDB770048BD24 /* pcm_success.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = pcm_success.c; sourceTree = ""; }; + 83D20077248DDB770048BD24 /* mups.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mups.c; sourceTree = ""; }; + 83D20078248DDB770048BD24 /* sadf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sadf.c; sourceTree = ""; }; + 83D20079248DDB770048BD24 /* sadl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sadl.c; sourceTree = ""; }; 83D2F58A2356B266007646ED /* libopus.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libopus.a; path = ../../ThirdParty/ffmpeg/lib/libopus.a; sourceTree = ""; }; 83D731381A74968900CA1366 /* g719.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = g719.xcodeproj; path = ../g719/g719.xcodeproj; sourceTree = ""; }; 83D7318B1A749EEE00CA1366 /* g719_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = g719_decoder.c; sourceTree = ""; }; @@ -1699,6 +1713,7 @@ 838BDB6D1D3B043C0022CA6F /* ffmpeg.c */, 836F6E4B18BDC2180095E648 /* ffw.c */, 8349A8FD1FE6257F00E26435 /* flx.c */, + 83D20072248DDB760048BD24 /* fsb_encrypted_streamfile.h */, 83A21F81201D8981000F04B9 /* fsb_encrypted.c */, 834FE0C4215C79E6000A5D3D /* fsb_interleave_streamfile.h */, 83A21F7E201D8980000F04B9 /* fsb_keys.h */, @@ -1734,9 +1749,11 @@ 836F6E5818BDC2180095E648 /* ivb.c */, 837CEAE923487F2B00E62A4A /* jstm_streamfile.h */, 837CEAEF23487F2C00E62A4A /* jstm.c */, + 83D20075248DDB760048BD24 /* kat.c */, 834FE0C3215C79E6000A5D3D /* kma9_streamfile.h */, 83A21F83201D8981000F04B9 /* kma9.c */, 836F6E5918BDC2180095E648 /* kraw.c */, + 83D20074248DDB760048BD24 /* ktsr.c */, 830EBE122004656E0023AA10 /* ktss.c */, 8373342423F60CDB00DE14DC /* kwb.c */, 8373341F23F60CDB00DE14DC /* lrmd_streamfile.h */, @@ -1764,6 +1781,8 @@ 83C7280322BC893A00678B4A /* mtaf.c */, 83031ED5243C510400C3F3E0 /* mul_streamfile.h */, 832BF81221E05149006F50F1 /* mul.c */, + 83D20073248DDB760048BD24 /* mups_streamfile.h */, + 83D20077248DDB770048BD24 /* mups.c */, 836F6E6218BDC2180095E648 /* mus_acm.c */, 83C7280622BC893B00678B4A /* mus_vc.c */, 836F6E6318BDC2180095E648 /* musc.c */, @@ -1776,7 +1795,6 @@ 836F6E6718BDC2180095E648 /* naomi_spsd.c */, 836F6E6818BDC2180095E648 /* nds_hwas.c */, 836F6E6918BDC2180095E648 /* nds_rrds.c */, - 836F6E6A18BDC2180095E648 /* nds_sad.c */, 830165991F256BD000CA0941 /* nds_strm_ffta2.c */, 836F6E6B18BDC2180095E648 /* nds_strm.c */, 836F6E6C18BDC2180095E648 /* nds_swav.c */, @@ -1822,6 +1840,7 @@ 8349A8F01FE6257C00E26435 /* pc_ast.c */, 836F6E8618BDC2180095E648 /* pc_mxst.c */, 8306B0D12098458F000302D4 /* pcm_sre.c */, + 83D20076248DDB770048BD24 /* pcm_success.c */, 836F6E8B18BDC2180095E648 /* pona.c */, 836F6E8C18BDC2180095E648 /* pos.c */, 8306B0C52098458D000302D4 /* ppst_streamfile.h */, @@ -1907,6 +1926,8 @@ 836F6EE918BDC2190095E648 /* rwx.c */, 836F6EEA18BDC2190095E648 /* s14_sss.c */, 8349A8F11FE6257D00E26435 /* sab.c */, + 83D20078248DDB770048BD24 /* sadf.c */, + 83D20079248DDB770048BD24 /* sadl.c */, 836F6EEB18BDC2190095E648 /* sat_baka.c */, 836F6EEC18BDC2190095E648 /* sat_dvi.c */, 836F6EED18BDC2190095E648 /* sat_sap.c */, @@ -2072,6 +2093,7 @@ 834FE0ED215C79ED000A5D3D /* fsb_interleave_streamfile.h in Headers */, 8351F32E2212B57000A606E4 /* ubi_bao_streamfile.h in Headers */, 8349A9111FE6258200E26435 /* bar_streamfile.h in Headers */, + 83D2007A248DDB770048BD24 /* fsb_encrypted_streamfile.h in Headers */, 836F6F2718BDC2190095E648 /* g72x_state.h in Headers */, 83FC176F23AC58D100E1025F /* cri_utf.h in Headers */, 83C7282822BC8C1500678B4A /* mixing.h in Headers */, @@ -2115,6 +2137,7 @@ 8373341D23F60C7B00DE14DC /* g7221_decoder_lib.h in Headers */, 8306B0E820984590000302D4 /* opus_interleave_streamfile.h in Headers */, 83031EDD243C510500C3F3E0 /* xnb_streamfile.h in Headers */, + 83D2007B248DDB770048BD24 /* mups_streamfile.h in Headers */, 8373341623F60C7B00DE14DC /* g7221_decoder_aes.h in Headers */, 8373341923F60C7B00DE14DC /* g7221_decoder_lib_data.h in Headers */, 83AFABBD23795202002F3947 /* ea_eaac_opus_streamfile.h in Headers */, @@ -2167,7 +2190,7 @@ 836F6B3018BDB8880095E648 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0940; + LastUpgradeCheck = 1150; ORGANIZATIONNAME = "Christopher Snowhill"; TargetAttributes = { 836F6B3818BDB8880095E648 = { @@ -2384,6 +2407,7 @@ 834FE105215C79ED000A5D3D /* xmd.c in Sources */, 837CEADA23487E8300E62A4A /* acb.c in Sources */, 834FE0F6215C79ED000A5D3D /* derf.c in Sources */, + 83D20081248DDB770048BD24 /* sadl.c in Sources */, 836F6F8B18BDC2190095E648 /* genh.c in Sources */, 83C7281922BC893D00678B4A /* fsb5_fev.c in Sources */, 8349A8EA1FE6253900E26435 /* blocked_ea_schl.c in Sources */, @@ -2475,7 +2499,9 @@ 836F6FD318BDC2190095E648 /* ps2_ccc.c in Sources */, 83C7281C22BC893D00678B4A /* sfh.c in Sources */, 834FE0FC215C79ED000A5D3D /* vai.c in Sources */, + 83D2007F248DDB770048BD24 /* mups.c in Sources */, 83AA5D171F6E2F600020821C /* mpeg_custom_utils_ealayer3.c in Sources */, + 83D20080248DDB770048BD24 /* sadf.c in Sources */, 83345A521F8AEB2800B2EAA4 /* xvag.c in Sources */, 836F6F3918BDC2190095E648 /* SASSC_decoder.c in Sources */, 8306B0A920984552000302D4 /* blocked_adm.c in Sources */, @@ -2507,7 +2533,6 @@ 83AA5D191F6E2F600020821C /* ea_xas_decoder.c in Sources */, 836F6F9318BDC2190095E648 /* ivaud.c in Sources */, 836F6F8518BDC2190095E648 /* exakt_sc.c in Sources */, - 836F6FA618BDC2190095E648 /* nds_sad.c in Sources */, 8306B0F120984590000302D4 /* ppst.c in Sources */, 832BF81C21E0514B006F50F1 /* xpcm.c in Sources */, 836F702B18BDC2190095E648 /* sdt.c in Sources */, @@ -2726,6 +2751,7 @@ 836F6FEA18BDC2190095E648 /* ps2_msa.c in Sources */, 836F6F3618BDC2190095E648 /* ogg_vorbis_decoder.c in Sources */, 8306B0E520984590000302D4 /* ubi_lyn.c in Sources */, + 83D2007D248DDB770048BD24 /* kat.c in Sources */, 836F6F7618BDC2190095E648 /* brstm.c in Sources */, 836F700718BDC2190095E648 /* ps2_vgv.c in Sources */, 836F704F18BDC2190095E648 /* xwb.c in Sources */, @@ -2753,6 +2779,7 @@ 83A21F8C201D8982000F04B9 /* kma9.c in Sources */, 8342469420C4D23000926E48 /* h4m.c in Sources */, 834FE111215C79ED000A5D3D /* ck.c in Sources */, + 83D2007C248DDB770048BD24 /* ktsr.c in Sources */, 836F704E18BDC2190095E648 /* xss.c in Sources */, 836F6FD118BDC2190095E648 /* ps2_bg00.c in Sources */, 836F6F4118BDC2190095E648 /* blocked.c in Sources */, @@ -2772,6 +2799,7 @@ 836F6FBA18BDC2190095E648 /* ngc_ymf.c in Sources */, 83F1EE2E245D4FB20076E182 /* vadpcm_decoder.c in Sources */, 836F705018BDC2190095E648 /* ydsp.c in Sources */, + 83D2007E248DDB770048BD24 /* pcm_success.c in Sources */, 8306B0B720984552000302D4 /* blocked_str_snds.c in Sources */, 836F702718BDC2190095E648 /* sat_baka.c in Sources */, 832389501D2246C300482226 /* hca.c in Sources */, @@ -2956,7 +2984,7 @@ 836F6B6218BDB8880095E648 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = ""; @@ -2986,7 +3014,7 @@ 836F6B6318BDB8880095E648 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = ""; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/coding.h b/Frameworks/vgmstream/vgmstream/src/coding/coding.h index 7367d292e..f12fd89a3 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/coding.h +++ b/Frameworks/vgmstream/vgmstream/src/coding/coding.h @@ -348,6 +348,7 @@ uint32_t ffmpeg_get_channel_layout(ffmpeg_codec_data * data); void ffmpeg_set_channel_remapping(ffmpeg_codec_data * data, int *channels_remap); const char* ffmpeg_get_codec_name(ffmpeg_codec_data * data); void ffmpeg_set_force_seek(ffmpeg_codec_data * data); +const char* ffmpeg_get_metadata_value(ffmpeg_codec_data* data, const char* key); /* ffmpeg_decoder_utils.c (helper-things) */ diff --git a/Frameworks/vgmstream/vgmstream/src/coding/coding_utils.c b/Frameworks/vgmstream/vgmstream/src/coding/coding_utils.c index f0e482431..0b6b3bfa5 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/coding_utils.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/coding_utils.c @@ -155,6 +155,8 @@ int ffmpeg_make_riff_xma1(uint8_t * buf, size_t buf_size, size_t sample_count, s put_16bitLE(buf+off+0x12, speakers); } + /* xmaencode decoding rejects XMA1 without "seek" chunk, though it doesn't seem to use it */ + memcpy(buf+riff_size-4-4, "data", 4); put_32bitLE(buf+riff_size-4, data_size); /* data size */ diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c index 13a3a02e1..1070921fe 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c @@ -941,4 +941,22 @@ void ffmpeg_set_force_seek(ffmpeg_codec_data * data) { //stream = data->formatCtx->streams[data->streamIndex]; } +const char* ffmpeg_get_metadata_value(ffmpeg_codec_data* data, const char* key) { + AVDictionary* avd; + AVDictionaryEntry* avde; + + if (!data || !data->codec) + return NULL; + + avd = data->formatCtx->streams[data->streamIndex]->metadata; + if (!avd) + return NULL; + + avde = av_dict_get(avd, key, NULL, AV_DICT_IGNORE_SUFFIX); + if (!avde) + return NULL; + + return avde->value; +} + #endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c index 7825db657..9f96d575f 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c @@ -11,7 +11,7 @@ * - expand type: IMA style or variations; low or high nibble first */ -static const int ADPCMTable[89] = { +static const int ADPCMTable[90] = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, @@ -23,7 +23,9 @@ static const int ADPCMTable[89] = { 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, - 32767 + 32767, + + 0 /* garbage value for Ubisoft IMA (see blocked_ubi_sce.c) */ }; static const int IMA_IndexTable[16] = { @@ -1054,10 +1056,14 @@ void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa if (has_header) { first_sample -= 10; //todo fix hack (needed to adjust nibble offset below) - } - if (step_index < 0) step_index=0; - if (step_index > 88) step_index=88; + if (step_index < 0) step_index = 0; + if (step_index > 88) step_index = 88; + } else { + if (step_index < 0) step_index = 0; + if (step_index > 89) step_index = 89; + } + for (i = first_sample; i < first_sample + samples_to_do; i++, sample_count += channelspacing) { off_t byte_offset = channelspacing == 1 ? diff --git a/Frameworks/vgmstream/vgmstream/src/formats.c b/Frameworks/vgmstream/vgmstream/src/formats.c index f924c7e98..8288c0032 100644 --- a/Frameworks/vgmstream/vgmstream/src/formats.c +++ b/Frameworks/vgmstream/vgmstream/src/formats.c @@ -106,6 +106,7 @@ static const char* extension_list[] = { "bik2", //"bin", //common "bk2", + "blk", "bmdx", "bms", "bnk", @@ -137,6 +138,7 @@ static const char* extension_list[] = { "cxs", "da", + "dat", "data", "dax", "dbm", @@ -237,6 +239,7 @@ static const char* extension_list[] = { "joe", "jstm", + "kat", "kces", "kcey", //fake extension/header id for .pcm (renamed, to be removed) "khv", //fake extension/header id for .vas (renamed, to be removed) @@ -244,6 +247,7 @@ static const char* extension_list[] = { "kovs", //fake extension/header id for .kvs "kns", "kraw", + "ktsl2asbin", "ktss", //fake extension/header id for .kns "kvs", @@ -322,6 +326,7 @@ static const char* extension_list[] = { "mta2", "mtaf", "mul", + "mups", "mus", "musc", "musx", @@ -1141,7 +1146,7 @@ static const meta_info meta_info_list[] = { {meta_CSTM, "Nintendo CSTM Header"}, {meta_FSTM, "Nintendo FSTM Header"}, {meta_KT_WIIBGM, "Koei Tecmo WiiBGM Header"}, - {meta_KTSS, "Koei Tecmo Nintendo Stream KTSS Header"}, + {meta_KTSS, "Koei Tecmo KTSS header"}, {meta_IDSP_NAMCO, "Namco IDSP header"}, {meta_WIIU_BTSND, "Nintendo Wii U Menu Boot Sound"}, {meta_MCA, "Capcom MCA header"}, @@ -1218,7 +1223,7 @@ static const meta_info meta_info_list[] = { {meta_UBI_BAO, "Ubisoft BAO header"}, {meta_DSP_SWITCH_AUDIO, "UE4 Switch Audio header"}, {meta_TA_AAC_VITA, "tri-Ace AAC (Vita) header"}, - {meta_DSP_SADF, "Procyon Studio SADF header"}, + {meta_SADF, "Procyon Studio SADF header"}, {meta_H4M, "Hudson HVQM4 header"}, {meta_ASF, "Argonaut ASF header"}, {meta_XMD, "Konami XMD header"}, @@ -1292,7 +1297,9 @@ static const meta_info meta_info_list[] = { {meta_WWISE_FX, "Audiokinetic Wwise FX header"}, {meta_DIVA, "DIVA header"}, {meta_IMUSE, "LucasArts iMUSE header"}, - + {meta_KTSR, "Koei Tecmo KTSR header"}, + {meta_KAT, "Sega KAT header"}, + {meta_PCM_SUCCESS, "Success PCM header"}, }; void get_vgmstream_coding_description(VGMSTREAM *vgmstream, char *out, size_t out_size) { diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked_ubi_sce.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked_ubi_sce.c index 7c848fd9a..640aca39d 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/blocked_ubi_sce.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked_ubi_sce.c @@ -44,13 +44,13 @@ void block_update_ubi_sce(off_t block_offset, VGMSTREAM* vgmstream) { vgmstream->ch[i].adpcm_step_index = read_32bitLE(block_offset + header_size * i + 0x04, sf); vgmstream->ch[i].adpcm_history1_32 = read_32bitLE(block_offset + header_size * i + 0x08, sf); - /* TODO figure out - * First step seems to always be a special value for the decoder, unsure of meaning. - * 0 = too quiet and max = 88 = waveform starts a bit off and clicky. First hist is usually +-1, - * other frames look, fine not sure what are they aiming for. - */ + /* First step is always 0x500, not sure if it's a bug or a feature but the game just takes it as is and + * ends up reading 0 from out-of-bounds memory area which causes a pop at the start. Yikes. + * It gets clampled later so the rest of the sound plays ok. + * We put 89 here as our special index which contains 0 to simulate this. + */ if (vgmstream->ch[i].adpcm_step_index == 0x500) { - vgmstream->ch[i].adpcm_step_index = 88; + vgmstream->ch[i].adpcm_step_index = 89; } } } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/acb.c b/Frameworks/vgmstream/vgmstream/src/meta/acb.c index 18b365d9d..e143ce8f6 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/acb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/acb.c @@ -1,641 +1,670 @@ -#include "meta.h" -#include "../coding/coding.h" -#include "cri_utf.h" - - -/* ACB (Atom Cue sheet Binary) - CRI container of memory audio, often together with a .awb wave bank */ -VGMSTREAM * init_vgmstream_acb(STREAMFILE *streamFile) { - VGMSTREAM *vgmstream = NULL; - STREAMFILE *temp_streamFile = NULL; - off_t subfile_offset; - size_t subfile_size; - utf_context *utf = NULL; - - - /* checks */ - if (!check_extensions(streamFile, "acb")) - goto fail; - if (read_32bitBE(0x00,streamFile) != 0x40555446) /* "@UTF" */ - goto fail; - - /* .acb is a cue sheet that uses @UTF (CRI's generic table format) to store row/columns - * with complex info (cues, sequences, spatial info, etc). it can store a memory .awb - * (our target here), or reference external/streamed .awb (loaded elsewhere) - * we only want .awb with actual waves but may use .acb to get names */ - { - int rows; - const char* name; - uint32_t offset = 0, size = 0; - uint32_t table_offset = 0x00; - - utf = utf_open(streamFile, table_offset, &rows, &name); - if (!utf) goto fail; - - if (rows != 1 || strcmp(name, "Header") != 0) - goto fail; - - //todo acb+cpk is also possible - - if (!utf_query_data(utf, 0, "AwbFile", &offset, &size)) - goto fail; - - subfile_offset = table_offset + offset; - subfile_size = size; - - /* column exists but can be empty */ - if (subfile_size == 0) - goto fail; - } - - //;VGM_LOG("ACB: subfile offset=%lx + %x\n", subfile_offset, subfile_size); - - temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, "awb"); - if (!temp_streamFile) goto fail; - - vgmstream = init_vgmstream_awb_memory(temp_streamFile, streamFile); - if (!vgmstream) goto fail; - - /* name-loading for this for memory .awb will be called from init_vgmstream_awb_memory */ - - utf_close(utf); - close_streamfile(temp_streamFile); - return vgmstream; - -fail: - utf_close(utf); - close_streamfile(temp_streamFile); - close_vgmstream(vgmstream); - return NULL; -} - -/* ************************************** */ - -//todo maybe use reopen sf? since internal buffer is going to be read -#define ACB_TABLE_BUFFER_SIZE 0x4000 - -STREAMFILE* setup_acb_streamfile(STREAMFILE *sf, size_t buffer_size) { - STREAMFILE *new_sf = NULL; - - new_sf = open_wrap_streamfile(sf); - new_sf = open_buffer_streamfile_f(new_sf, buffer_size); - return new_sf; -} - - -typedef struct { - STREAMFILE *acbFile; /* original reference, don't close */ - - /* keep track of these tables so they can be closed when done */ - utf_context *Header; - - utf_context *CueNameTable; - utf_context *CueTable; - utf_context *BlockTable; - utf_context *SequenceTable; - utf_context *TrackTable; - utf_context *TrackCommandTable; - utf_context *SynthTable; - utf_context *WaveformTable; - - STREAMFILE *CueNameSf; - STREAMFILE *CueSf; - STREAMFILE *BlockSf; - STREAMFILE *SequenceSf; - STREAMFILE *TrackSf; - STREAMFILE *TrackCommandSf; - STREAMFILE *SynthSf; - STREAMFILE *WaveformSf; - - /* config */ - int is_memory; - int target_waveid; - int has_TrackEventTable; - int has_CommandTable; - - /* to avoid infinite/circular references (AtomViewer crashes otherwise) */ - int synth_depth; - int sequence_depth; - - /* name stuff */ - int16_t cuename_index; - const char * cuename_name; - int awbname_count; - int16_t awbname_list[255]; - char name[1024]; - -} acb_header; - -static int open_utf_subtable(acb_header* acb, STREAMFILE* *TableSf, utf_context* *Table, const char* TableName, int* rows) { - uint32_t offset = 0; - - /* already loaded */ - if (*Table != NULL) - return 1; - - if (!utf_query_data(acb->Header, 0, TableName, &offset, NULL)) - goto fail; - - /* open a buffered streamfile to avoid so much IO back and forth between all the tables */ - *TableSf = setup_acb_streamfile(acb->acbFile, ACB_TABLE_BUFFER_SIZE); - if (!*TableSf) goto fail; - - *Table = utf_open(*TableSf, offset, rows, NULL); - if (!*Table) goto fail; - - //;VGM_LOG("ACB: loaded table %s\n", TableName); - return 1; -fail: - return 0; -} - - -static void add_acb_name(acb_header* acb, int8_t Waveform_Streaming) { - //todo safe string ops - - /* ignore name repeats */ - if (acb->awbname_count) { - int i; - for (i = 0; i < acb->awbname_count; i++) { - if (acb->awbname_list[i] == acb->cuename_index) - return; - } - } - - /* since waveforms can be reused by cues multiple names are a thing */ - if (acb->awbname_count) { - strcat(acb->name, "; "); - strcat(acb->name, acb->cuename_name); - } - else { - strcpy(acb->name, acb->cuename_name); - } - if (Waveform_Streaming == 2 && acb->is_memory) { - strcat(acb->name, " [pre]"); - } - - acb->awbname_list[acb->awbname_count] = acb->cuename_index; - acb->awbname_count++; - if (acb->awbname_count >= 254) - acb->awbname_count = 254; /* ??? */ - - //;VGM_LOG("ACB: found cue for waveid=%i: %s\n", acb->target_waveid, acb->cuename_name); -} - - -static int load_acb_waveform(acb_header* acb, int16_t Index) { - uint16_t Waveform_Id; - uint8_t Waveform_Streaming; - - /* read Waveform[Index] */ - if (!open_utf_subtable(acb, &acb->WaveformSf, &acb->WaveformTable, "WaveformTable", NULL)) - goto fail; - if (!utf_query_u16(acb->WaveformTable, Index, "Id", &Waveform_Id)) { /* older versions use Id */ - if (acb->is_memory) { - if (!utf_query_u16(acb->WaveformTable, Index, "MemoryAwbId", &Waveform_Id)) - goto fail; - } else { - if (!utf_query_u16(acb->WaveformTable, Index, "StreamAwbId", &Waveform_Id)) - goto fail; - } - } - if (!utf_query_u8(acb->WaveformTable, Index, "Streaming", &Waveform_Streaming)) - goto fail; - //;VGM_LOG("ACB: Waveform[%i]: Id=%i, Streaming=%i\n", Index, Waveform_Id, Waveform_Streaming); - - /* not found but valid */ - if (Waveform_Id != acb->target_waveid) - return 1; - /* must match our target's (0=memory, 1=streaming, 2=memory (prefetch)+stream) */ - if ((acb->is_memory && Waveform_Streaming == 1) || (!acb->is_memory && Waveform_Streaming == 0)) - return 1; - - /* aaand finally get name (phew) */ - add_acb_name(acb, Waveform_Streaming); - - return 1; -fail: - return 0; -} - -/* define here for Synths pointing to Sequences */ -static int load_acb_sequence(acb_header* acb, int16_t Index); - -static int load_acb_synth(acb_header* acb, int16_t Index) { - int i, count; - uint8_t Synth_Type; - uint32_t Synth_ReferenceItems_offset; - uint32_t Synth_ReferenceItems_size; - - - /* read Synth[Index] */ - if (!open_utf_subtable(acb, &acb->SynthSf, &acb->SynthTable, "SynthTable", NULL)) - goto fail; - if (!utf_query_u8(acb->SynthTable, Index, "Type", &Synth_Type)) - goto fail; - if (!utf_query_data(acb->SynthTable, Index, "ReferenceItems", &Synth_ReferenceItems_offset, &Synth_ReferenceItems_size)) - goto fail; - //;VGM_LOG("ACB: Synth[%i]: Type=%x, ReferenceItems={%x,%x}\n", Index, Synth_Type, Synth_ReferenceItems_offset, Synth_ReferenceItems_size); - - acb->synth_depth++; - - if (acb->synth_depth > 2) { - VGM_LOG("ACB: Synth depth too high\n"); - goto fail; /* max Synth > Synth > Waveform (ex. Yakuza 6) */ - } - - /* Cue.ReferenceType 2 uses Synth.Type, while 3 always sets it to 0 and uses Sequence.Type instead - * Both look the same and probably affect which item in the ReferenceItems list is picked: - * - 0: polyphonic (1 item) - * - 1: sequential (1 to N?) - * - 2: shuffle (1 from N?) - * - 3: random (1 from N?) - * - 4: random no repeat - * - 5: switch game variable - * - 6: combo sequential - * - 7: switch selector - * - 8: track transition by selector - * - other: undefined? - * Since we want to find all possible Waveforms that could match our id, we ignore Type and just parse all ReferenceItems. - */ - - count = Synth_ReferenceItems_size / 0x04; - for (i = 0; i < count; i++) { - uint16_t Synth_ReferenceItem_type = read_u16be(Synth_ReferenceItems_offset + i*0x04 + 0x00, acb->SynthSf); - uint16_t Synth_ReferenceItem_index = read_u16be(Synth_ReferenceItems_offset + i*0x04 + 0x02, acb->SynthSf); - //;VGM_LOG("ACB: Synth.ReferenceItem: type=%x, index=%x\n", Synth_ReferenceItem_type, Synth_ReferenceItem_index); - - switch(Synth_ReferenceItem_type) { - case 0x00: /* no reference */ - count = 0; - break; - - case 0x01: /* Waveform (most common) */ - if (!load_acb_waveform(acb, Synth_ReferenceItem_index)) - goto fail; - break; - - case 0x02: /* Synth, possibly random (rare, found in Sonic Lost World with ReferenceType 2) */ - if (!load_acb_synth(acb, Synth_ReferenceItem_index)) - goto fail; - break; - - case 0x03: /* Sequence of Synths w/ % in Synth.TrackValues (rare, found in Sonic Lost World with ReferenceType 2) */ - if (!load_acb_sequence(acb, Synth_ReferenceItem_index)) - goto fail; - break; - - case 0x06: /* this seems to point to Synth but results don't make sense (rare, from Sonic Lost World) */ - default: /* undefined/crashes AtomViewer */ - VGM_LOG("ACB: unknown Synth.ReferenceItem type %x at %x + %x\n", Synth_ReferenceItem_type, Synth_ReferenceItems_offset, Synth_ReferenceItems_size); - count = 0; /* force end without failing */ - break; - } - } - - acb->synth_depth--; - - return 1; -fail: - return 0; -} - -static int load_acb_track_event_command(acb_header* acb, int16_t Index) { - uint16_t Track_EventIndex; - uint32_t Track_Command_offset; - uint32_t Track_Command_size; - - - /* read Track[Index] */ - if (!open_utf_subtable(acb, &acb->TrackSf, &acb->TrackTable, "TrackTable", NULL)) - goto fail; - if (!utf_query_u16(acb->TrackTable, Index, "EventIndex", &Track_EventIndex)) - goto fail; - //;VGM_LOG("ACB: Track[%i]: EventIndex=%i\n", Index, Track_EventIndex); - - /* next link varies with version, check by table existence */ - if (acb->has_CommandTable) { /* <=v1.27 */ - /* read Command[EventIndex] */ - if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "CommandTable", NULL)) - goto fail; - if (!utf_query_data(acb->TrackCommandTable, Track_EventIndex, "Command", &Track_Command_offset, &Track_Command_size)) - goto fail; - //;VGM_LOG("ACB: Command[%i]: Command={%x,%x}\n", Track_EventIndex, Track_Command_offset,Track_Command_size); - } - else if (acb->has_TrackEventTable) { /* >=v1.28 */ - /* read TrackEvent[EventIndex] */ - if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "TrackEventTable", NULL)) - goto fail; - if (!utf_query_data(acb->TrackCommandTable, Track_EventIndex, "Command", &Track_Command_offset, &Track_Command_size)) - goto fail; - //;VGM_LOG("ACB: TrackEvent[%i]: Command={%x,%x}\n", Track_EventIndex, Track_Command_offset,Track_Command_size); - } - else { - VGM_LOG("ACB: unknown command table\n"); - goto fail; - } - - /* read Command (some kind of multiple TLVs, this seems ok) */ - { - uint32_t offset = Track_Command_offset; - uint32_t max_offset = Track_Command_offset + Track_Command_size; - uint16_t tlv_code, tlv_type, tlv_index; - uint8_t tlv_size; - - - while (offset < max_offset) { - tlv_code = read_u16be(offset + 0x00, acb->TrackCommandSf); - tlv_size = read_u8 (offset + 0x02, acb->TrackCommandSf); - offset += 0x03; - - if (tlv_code == 0x07D0) { - if (tlv_size < 0x04) { - VGM_LOG("ACB: TLV with unknown size\n"); - break; - } - - tlv_type = read_u16be(offset + 0x00, acb->TrackCommandSf); - tlv_index = read_u16be(offset + 0x02, acb->TrackCommandSf); - //;VGM_LOG("ACB: TLV at %x: type %x, index=%x\n", offset, tlv_type, tlv_index); - - /* probably same as Synth_ReferenceItem_type */ - switch(tlv_type) { - - case 0x02: /* Synth (common) */ - if (!load_acb_synth(acb, tlv_index)) - goto fail; - break; - - case 0x03: /* Sequence of Synths (common, ex. Yakuza 6, Yakuza Kiwami 2) */ - if (!load_acb_sequence(acb, tlv_index)) - goto fail; - break; - - /* possible values? (from debug): - * - sequence - * - track - * - synth - * - trackEvent - * - seqParameterPallet, - * - trackParameterPallet, - * - synthParameterPallet, - */ - default: - VGM_LOG("ACB: unknown TLV type %x at %x + %x\n", tlv_type, offset, tlv_size); - max_offset = 0; /* force end without failing */ - break; - } - } - - /* 0x07D1 comes suspiciously often paired with 0x07D0 too */ - - offset += tlv_size; - } - } - - return 1; -fail: - return 0; -} - -static int load_acb_sequence(acb_header* acb, int16_t Index) { - int i; - uint16_t Sequence_NumTracks; - uint32_t Sequence_TrackIndex_offset; - uint32_t Sequence_TrackIndex_size; - - - /* read Sequence[Index] */ - if (!open_utf_subtable(acb, &acb->SequenceSf, &acb->SequenceTable, "SequenceTable", NULL)) - goto fail; - if (!utf_query_u16(acb->SequenceTable, Index, "NumTracks", &Sequence_NumTracks)) - goto fail; - if (!utf_query_data(acb->SequenceTable, Index, "TrackIndex", &Sequence_TrackIndex_offset, &Sequence_TrackIndex_size)) - goto fail; - //;VGM_LOG("ACB: Sequence[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, Sequence_NumTracks, Sequence_TrackIndex_offset,Sequence_TrackIndex_size); - - acb->sequence_depth++; - - if (acb->sequence_depth > 3) { - VGM_LOG("ACB: Sequence depth too high\n"); - goto fail; /* max Sequence > Sequence > Sequence > Synth > Waveform (ex. Yakuza 6) */ - } - - if (Sequence_NumTracks * 0x02 > Sequence_TrackIndex_size) { /* padding may exist */ - VGM_LOG("ACB: wrong Sequence.TrackIndex size\n"); - goto fail; - } - - /* read Tracks inside Sequence */ - for (i = 0; i < Sequence_NumTracks; i++) { - int16_t Sequence_TrackIndex_index = read_s16be(Sequence_TrackIndex_offset + i*0x02, acb->SequenceSf); - - if (!load_acb_track_event_command(acb, Sequence_TrackIndex_index)) - goto fail; - } - - acb->sequence_depth--; - - return 1; -fail: - return 0; -} - -static int load_acb_block(acb_header* acb, int16_t Index) { - int i; - uint16_t Block_NumTracks; - uint32_t Block_TrackIndex_offset; - uint32_t Block_TrackIndex_size; - - - /* read Block[Index] */ - if (!open_utf_subtable(acb, &acb->BlockSf, &acb->BlockTable, "BlockTable", NULL)) - goto fail; - if (!utf_query_u16(acb->BlockTable, Index, "NumTracks", &Block_NumTracks)) - goto fail; - if (!utf_query_data(acb->BlockTable, Index, "TrackIndex", &Block_TrackIndex_offset, &Block_TrackIndex_size)) - goto fail; - //;VGM_LOG("ACB: Block[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, Block_NumTracks, Block_TrackIndex_offset,Block_TrackIndex_size); - - if (Block_NumTracks * 0x02 > Block_TrackIndex_size) { /* padding may exist */ - VGM_LOG("ACB: wrong Block.TrackIndex size\n"); - goto fail; - } - - /* read Tracks inside Block */ - for (i = 0; i < Block_NumTracks; i++) { - int16_t Block_TrackIndex_index = read_s16be(Block_TrackIndex_offset + i*0x02, acb->BlockSf); - - if (!load_acb_track_event_command(acb, Block_TrackIndex_index)) - goto fail; - } - - return 1; -fail: - return 0; - -} - -static int load_acb_cue(acb_header* acb, int16_t Index) { - uint8_t Cue_ReferenceType; - uint16_t Cue_ReferenceIndex; - - - /* read Cue[Index] */ - if (!open_utf_subtable(acb, &acb->CueSf, &acb->CueTable, "CueTable", NULL)) - goto fail; - if (!utf_query_u8(acb->CueTable, Index, "ReferenceType", &Cue_ReferenceType)) - goto fail; - if (!utf_query_u16(acb->CueTable, Index, "ReferenceIndex", &Cue_ReferenceIndex)) - goto fail; - //;VGM_LOG("ACB: Cue[%i]: ReferenceType=%i, ReferenceIndex=%i\n", Index, Cue_ReferenceType, Cue_ReferenceIndex); - - - /* usually older games use older references but not necessarily */ - switch(Cue_ReferenceType) { - - case 0x01: /* Cue > Waveform (ex. PES 2015) */ - if (!load_acb_waveform(acb, Cue_ReferenceIndex)) - goto fail; - break; - - case 0x02: /* Cue > Synth > Waveform (ex. Ukiyo no Roushi) */ - if (!load_acb_synth(acb, Cue_ReferenceIndex)) - goto fail; - break; - - case 0x03: /* Cue > Sequence > Track > Command > Synth > Waveform (ex. Valkyrie Profile anatomia, Yakuza Kiwami 2) */ - if (!load_acb_sequence(acb, Cue_ReferenceIndex)) - goto fail; - break; - - //todo "blockSequence"? - case 0x08: /* Cue > Block > Track > Command > Synth > Waveform (ex. Sonic Lost World, rare) */ - if (!load_acb_block(acb, Cue_ReferenceIndex)) - goto fail; - break; - - case 0x00: /* none */ - case 0x04: /* "track" */ - case 0x05: /* "outsideLink" */ - case 0x06: /* "insideLinkSynth" (ex. PES 2014) */ - case 0x07: /* "insideLinkSequence" (ex. PES 2014) */ - case 0x09: /* "insideLinkBlockSequence" */ - case 0x0a: /* "eventCue_UnUse" */ - case 0x0b: /* "soundGenerator" */ - default: - VGM_LOG("ACB: unknown Cue.ReferenceType=%x, Cue.ReferenceIndex=%x\n", Cue_ReferenceType, Cue_ReferenceIndex); - break; /* ignore and continue */ - } - - - return 1; -fail: - return 0; - -} - -static int load_acb_cuename(acb_header* acb, int16_t Index) { - uint16_t CueName_CueIndex; - const char* CueName_CueName; - - - /* read CueName[Index] */ - if (!open_utf_subtable(acb, &acb->CueNameSf, &acb->CueNameTable, "CueNameTable", NULL)) - goto fail; - if (!utf_query_u16(acb->CueNameTable, Index, "CueIndex", &CueName_CueIndex)) - goto fail; - if (!utf_query_string(acb->CueNameTable, Index, "CueName", &CueName_CueName)) - goto fail; - //;VGM_LOG("ACB: CueName[%i]: CueIndex=%i, CueName=%s\n", Index, CueName_CueIndex, CueName_CueName); - - - /* save as will be needed if references waveform */ - acb->cuename_index = Index; - acb->cuename_name = CueName_CueName; - - if (!load_acb_cue(acb, CueName_CueIndex)) - goto fail; - - return 1; -fail: - return 0; -} - - -void load_acb_wave_name(STREAMFILE *streamFile, VGMSTREAM* vgmstream, int waveid, int is_memory) { - acb_header acb = {0}; - int i, CueName_rows; - - - if (!streamFile || !vgmstream || waveid < 0) - return; - - /* Normally games load a .acb + .awb, and asks the .acb to play a cue by name or index. - * Since we only care for actual waves, to get its name we need to find which cue uses our wave. - * Multiple cues can use the same wave (meaning multiple names), and one cue may use multiple waves. - * There is no easy way to map cue name <> wave name so basically we parse the whole thing. - * - * .acb are created in CRI Atom Craft, where user defines N Cues with CueName each, then link somehow - * to a Waveform (.awb=streamed or memory .acb=internal, data 'material' encoded in some format), - * depending on reference types. Typical links are: - * - CueName > Cue > Waveform (type 1) - * - CueName > Cue > Synth > Waveform (type 2) - * - CueName > Cue > Sequence > Track > Command > Synth > Waveform (type 3, <=v1.27) - * - CueName > Cue > Sequence > Track > Command > Synth > Synth > Waveform (type 3, <=v1.27) - * - CueName > Cue > Sequence > Track > TrackEvent > Command > Synth > Waveform (type 3, >=v1.28) - * - CueName > Cue > Sequence > Track > TrackEvent > Command > Synth > Synth > Waveform (type 3, >=v1.28) - * - CueName > Cue > Sequence > Track > TrackEvent > Command > Sequence > (...) > Synth > Waveform (type 3, >=v1.28) - * - CueName > Cue > Block > Track > Command > Synth > Synth > Waveform (type 8) - * - others should be possible but haven't been observed - * Atom Craft may only target certain .acb versions so some links are later removed - * Not all cues to point to though Waveforms, as some are just config events/commands. - * .acb link to .awb by name (loaded manually), though they have a checksum/hash to validate. - */ - - //;VGM_LOG("ACB: find waveid=%i\n", waveid); - - acb.acbFile = streamFile; - - acb.Header = utf_open(acb.acbFile, 0x00, NULL, NULL); - if (!acb.Header) goto fail; - - acb.target_waveid = waveid; - acb.is_memory = is_memory; - acb.has_TrackEventTable = utf_query_data(acb.Header, 0, "TrackEventTable", NULL,NULL); - acb.has_CommandTable = utf_query_data(acb.Header, 0, "CommandTable", NULL,NULL); - - - /* read all possible cue names and find which waveids are referenced by it */ - if (!open_utf_subtable(&acb, &acb.CueNameSf, &acb.CueNameTable, "CueNameTable", &CueName_rows)) - goto fail; - for (i = 0; i < CueName_rows; i++) { - - if (!load_acb_cuename(&acb, i)) - goto fail; - } - - /* meh copy */ - if (acb.awbname_count > 0) { - strncpy(vgmstream->stream_name, acb.name, STREAM_NAME_SIZE); - vgmstream->stream_name[STREAM_NAME_SIZE - 1] = '\0'; - } - -fail: - utf_close(acb.Header); - - utf_close(acb.CueNameTable); - utf_close(acb.CueTable); - utf_close(acb.SequenceTable); - utf_close(acb.TrackTable); - utf_close(acb.TrackCommandTable); - utf_close(acb.SynthTable); - utf_close(acb.WaveformTable); - - close_streamfile(acb.CueNameSf); - close_streamfile(acb.CueSf); - close_streamfile(acb.SequenceSf); - close_streamfile(acb.TrackSf); - close_streamfile(acb.TrackCommandSf); - close_streamfile(acb.SynthSf); - close_streamfile(acb.WaveformSf); -} +#include "meta.h" +#include "../coding/coding.h" +#include "cri_utf.h" + + +/* ACB (Atom Cue sheet Binary) - CRI container of memory audio, often together with a .awb wave bank */ +VGMSTREAM* init_vgmstream_acb(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE* temp_sf = NULL; + off_t subfile_offset; + size_t subfile_size; + utf_context *utf = NULL; + + + /* checks */ + if (!check_extensions(sf, "acb")) + goto fail; + if (read_32bitBE(0x00,sf) != 0x40555446) /* "@UTF" */ + goto fail; + + /* .acb is a cue sheet that uses @UTF (CRI's generic table format) to store row/columns + * with complex info (cues, sequences, spatial info, etc). it can store a memory .awb + * (our target here), or reference external/streamed .awb (loaded elsewhere) + * we only want .awb with actual waves but may use .acb to get names */ + { + int rows; + const char* name; + uint32_t offset = 0, size = 0; + uint32_t table_offset = 0x00; + + utf = utf_open(sf, table_offset, &rows, &name); + if (!utf) goto fail; + + if (rows != 1 || strcmp(name, "Header") != 0) + goto fail; + + //todo acb+cpk is also possible + + if (!utf_query_data(utf, 0, "AwbFile", &offset, &size)) + goto fail; + + subfile_offset = table_offset + offset; + subfile_size = size; + + /* column exists but can be empty */ + if (subfile_size == 0) + goto fail; + } + + //;VGM_LOG("ACB: subfile offset=%lx + %x\n", subfile_offset, subfile_size); + + temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, "awb"); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_awb_memory(temp_sf, sf); + if (!vgmstream) goto fail; + + /* name-loading for this for memory .awb will be called from init_vgmstream_awb_memory */ + + utf_close(utf); + close_streamfile(temp_sf); + return vgmstream; + +fail: + utf_close(utf); + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} + +/* ************************************** */ + +/* extra config for .acb with lots of sounds, since there is a lot of IO back and forth, + * ex. +7000 acb+awb subsongs in Ultra Despair Girls (PC) */ +//TODO: could pre-load all sections first, but needs cache for multiple subsongs (+semaphs, if multiple read the same thing) +#define ACB_TABLE_BUFFER_CUENAME 0x8000 +#define ACB_TABLE_BUFFER_CUE 0x40000 +#define ACB_TABLE_BUFFER_BLOCK 0x8000 +#define ACB_TABLE_BUFFER_SEQUENCE 0x40000 +#define ACB_TABLE_BUFFER_TRACK 0x10000 +#define ACB_TABLE_BUFFER_TRACKCOMMAND 0x20000 +#define ACB_TABLE_BUFFER_SYNTH 0x40000 +#define ACB_TABLE_BUFFER_WAVEFORM 0x20000 + +#define ACB_MAX_NAMELIST 255 +#define ACB_MAX_NAME 1024 /* even more is possible in rare cases [Senran Kagura Burst Re:Newal (PC)] */ + + +STREAMFILE* setup_acb_streamfile(STREAMFILE* sf, size_t buffer_size) { + STREAMFILE* new_sf = NULL; + + /* buffer seems better than reopening when opening multiple subsongs at the same time with STDIO, + * even though there is more buffer trashing, maybe concurrent IO is slower */ + new_sf = open_wrap_streamfile(sf); + new_sf = open_buffer_streamfile_f(new_sf, buffer_size); + //new_sf = reopen_streamfile(sf, buffer_size); + return new_sf; +} + + +typedef struct { + STREAMFILE* acbFile; /* original reference, don't close */ + + /* keep track of these tables so they can be closed when done */ + utf_context *Header; + + utf_context *CueNameTable; + utf_context *CueTable; + utf_context *BlockTable; + utf_context *SequenceTable; + utf_context *TrackTable; + utf_context *TrackCommandTable; + utf_context *SynthTable; + utf_context *WaveformTable; + + STREAMFILE* CueNameSf; + STREAMFILE* CueSf; + STREAMFILE* BlockSf; + STREAMFILE* SequenceSf; + STREAMFILE* TrackSf; + STREAMFILE* TrackCommandSf; + STREAMFILE* SynthSf; + STREAMFILE* WaveformSf; + + /* config */ + int is_memory; + int target_waveid; + int has_TrackEventTable; + int has_CommandTable; + + /* to avoid infinite/circular references (AtomViewer crashes otherwise) */ + int synth_depth; + int sequence_depth; + + /* name stuff */ + int16_t cuename_index; + const char * cuename_name; + int awbname_count; + int16_t awbname_list[ACB_MAX_NAMELIST]; + char name[ACB_MAX_NAME]; + +} acb_header; + +static int open_utf_subtable(acb_header* acb, STREAMFILE* *TableSf, utf_context* *Table, const char* TableName, int* rows, int buffer) { + uint32_t offset = 0; + + /* already loaded */ + if (*Table != NULL) + return 1; + + if (!utf_query_data(acb->Header, 0, TableName, &offset, NULL)) + goto fail; + + *TableSf = setup_acb_streamfile(acb->acbFile, buffer); + if (!*TableSf) goto fail; + + *Table = utf_open(*TableSf, offset, rows, NULL); + if (!*Table) goto fail; + + //;VGM_LOG("ACB: loaded table %s\n", TableName); + //;VGM_LOG("ACB: sf=%x\n", (uint32_t)*TableSf); + return 1; +fail: + return 0; +} + +//todo safeops, avoid recalc lens +static void acb_cat(char* dst, int dst_max, const char* src) { + int dst_len = strlen(dst); + int src_len = strlen(dst); + if (dst_len + src_len > dst_max - 1) + return; + strcat(dst, src); +} +static void acb_cpy(char* dst, int dst_max, const char* src) { + int src_len = strlen(dst); + if (src_len > dst_max - 1) + return; + strcpy(dst, src); +} + +static void add_acb_name(acb_header* acb, int8_t Waveform_Streaming) { + + /* ignore name repeats */ + if (acb->awbname_count) { + int i; + for (i = 0; i < acb->awbname_count; i++) { + if (acb->awbname_list[i] == acb->cuename_index) + return; + } + } + + /* since waveforms can be reused by cues, multiple names are a thing */ + if (acb->awbname_count) { + acb_cat(acb->name, sizeof(acb->name), "; "); + acb_cat(acb->name, sizeof(acb->name), acb->cuename_name); + } + else { + acb_cpy(acb->name, sizeof(acb->name), acb->cuename_name); + } + if (Waveform_Streaming == 2 && acb->is_memory) { + acb_cat(acb->name, sizeof(acb->name), " [pre]"); + } + + acb->awbname_list[acb->awbname_count] = acb->cuename_index; + acb->awbname_count++; + if (acb->awbname_count >= ACB_MAX_NAMELIST) + acb->awbname_count = ACB_MAX_NAMELIST - 1; /* ??? */ + + //;VGM_LOG("ACB: found cue for waveid=%i: %s\n", acb->target_waveid, acb->cuename_name); +} + + +static int load_acb_waveform(acb_header* acb, int16_t Index) { + uint16_t Waveform_Id; + uint8_t Waveform_Streaming; + + /* read Waveform[Index] */ + if (!open_utf_subtable(acb, &acb->WaveformSf, &acb->WaveformTable, "WaveformTable", NULL, ACB_TABLE_BUFFER_WAVEFORM)) + goto fail; + if (!utf_query_u16(acb->WaveformTable, Index, "Id", &Waveform_Id)) { /* older versions use Id */ + if (acb->is_memory) { + if (!utf_query_u16(acb->WaveformTable, Index, "MemoryAwbId", &Waveform_Id)) + goto fail; + } else { + if (!utf_query_u16(acb->WaveformTable, Index, "StreamAwbId", &Waveform_Id)) + goto fail; + } + } + if (!utf_query_u8(acb->WaveformTable, Index, "Streaming", &Waveform_Streaming)) + goto fail; + //;VGM_LOG("ACB: Waveform[%i]: Id=%i, Streaming=%i\n", Index, Waveform_Id, Waveform_Streaming); + + /* not found but valid */ + if (Waveform_Id != acb->target_waveid) + return 1; + /* must match our target's (0=memory, 1=streaming, 2=memory (prefetch)+stream) */ + if ((acb->is_memory && Waveform_Streaming == 1) || (!acb->is_memory && Waveform_Streaming == 0)) + return 1; + + /* aaand finally get name (phew) */ + add_acb_name(acb, Waveform_Streaming); + + return 1; +fail: + return 0; +} + +/* define here for Synths pointing to Sequences */ +static int load_acb_sequence(acb_header* acb, int16_t Index); + +static int load_acb_synth(acb_header* acb, int16_t Index) { + int i, count; + uint8_t Synth_Type; + uint32_t Synth_ReferenceItems_offset; + uint32_t Synth_ReferenceItems_size; + + + /* read Synth[Index] */ + if (!open_utf_subtable(acb, &acb->SynthSf, &acb->SynthTable, "SynthTable", NULL, ACB_TABLE_BUFFER_SYNTH)) + goto fail; + if (!utf_query_u8(acb->SynthTable, Index, "Type", &Synth_Type)) + goto fail; + if (!utf_query_data(acb->SynthTable, Index, "ReferenceItems", &Synth_ReferenceItems_offset, &Synth_ReferenceItems_size)) + goto fail; + //;VGM_LOG("ACB: Synth[%i]: Type=%x, ReferenceItems={%x,%x}\n", Index, Synth_Type, Synth_ReferenceItems_offset, Synth_ReferenceItems_size); + + acb->synth_depth++; + + if (acb->synth_depth > 2) { + VGM_LOG("ACB: Synth depth too high\n"); + goto fail; /* max Synth > Synth > Waveform (ex. Yakuza 6) */ + } + + /* Cue.ReferenceType 2 uses Synth.Type, while 3 always sets it to 0 and uses Sequence.Type instead + * Both look the same and probably affect which item in the ReferenceItems list is picked: + * - 0: polyphonic (1 item) + * - 1: sequential (1 to N?) + * - 2: shuffle (1 from N?) + * - 3: random (1 from N?) + * - 4: random no repeat + * - 5: switch game variable + * - 6: combo sequential + * - 7: switch selector + * - 8: track transition by selector + * - other: undefined? + * Since we want to find all possible Waveforms that could match our id, we ignore Type and just parse all ReferenceItems. + */ + + count = Synth_ReferenceItems_size / 0x04; + for (i = 0; i < count; i++) { + uint16_t Synth_ReferenceItem_type = read_u16be(Synth_ReferenceItems_offset + i*0x04 + 0x00, acb->SynthSf); + uint16_t Synth_ReferenceItem_index = read_u16be(Synth_ReferenceItems_offset + i*0x04 + 0x02, acb->SynthSf); + //;VGM_LOG("ACB: Synth.ReferenceItem: type=%x, index=%x\n", Synth_ReferenceItem_type, Synth_ReferenceItem_index); + + switch(Synth_ReferenceItem_type) { + case 0x00: /* no reference */ + count = 0; + break; + + case 0x01: /* Waveform (most common) */ + if (!load_acb_waveform(acb, Synth_ReferenceItem_index)) + goto fail; + break; + + case 0x02: /* Synth, possibly random (rare, found in Sonic Lost World with ReferenceType 2) */ + if (!load_acb_synth(acb, Synth_ReferenceItem_index)) + goto fail; + break; + + case 0x03: /* Sequence of Synths w/ % in Synth.TrackValues (rare, found in Sonic Lost World with ReferenceType 2) */ + if (!load_acb_sequence(acb, Synth_ReferenceItem_index)) + goto fail; + break; + + case 0x06: /* this seems to point to Synth but results don't make sense (rare, from Sonic Lost World) */ + default: /* undefined/crashes AtomViewer */ + VGM_LOG("ACB: unknown Synth.ReferenceItem type %x at %x + %x\n", Synth_ReferenceItem_type, Synth_ReferenceItems_offset, Synth_ReferenceItems_size); + count = 0; /* force end without failing */ + break; + } + } + + acb->synth_depth--; + + return 1; +fail: + return 0; +} + +static int load_acb_track_event_command(acb_header* acb, int16_t Index) { + uint16_t Track_EventIndex; + uint32_t Track_Command_offset; + uint32_t Track_Command_size; + + + /* read Track[Index] */ + if (!open_utf_subtable(acb, &acb->TrackSf, &acb->TrackTable, "TrackTable", NULL, ACB_TABLE_BUFFER_TRACK )) + goto fail; + if (!utf_query_u16(acb->TrackTable, Index, "EventIndex", &Track_EventIndex)) + goto fail; + //;VGM_LOG("ACB: Track[%i]: EventIndex=%i\n", Index, Track_EventIndex); + + /* next link varies with version, check by table existence */ + if (acb->has_CommandTable) { /* <=v1.27 */ + /* read Command[EventIndex] */ + if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "CommandTable", NULL, ACB_TABLE_BUFFER_TRACKCOMMAND)) + goto fail; + if (!utf_query_data(acb->TrackCommandTable, Track_EventIndex, "Command", &Track_Command_offset, &Track_Command_size)) + goto fail; + //;VGM_LOG("ACB: Command[%i]: Command={%x,%x}\n", Track_EventIndex, Track_Command_offset,Track_Command_size); + } + else if (acb->has_TrackEventTable) { /* >=v1.28 */ + /* read TrackEvent[EventIndex] */ + if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "TrackEventTable", NULL, ACB_TABLE_BUFFER_TRACKCOMMAND)) + goto fail; + if (!utf_query_data(acb->TrackCommandTable, Track_EventIndex, "Command", &Track_Command_offset, &Track_Command_size)) + goto fail; + //;VGM_LOG("ACB: TrackEvent[%i]: Command={%x,%x}\n", Track_EventIndex, Track_Command_offset,Track_Command_size); + } + else { + VGM_LOG("ACB: unknown command table\n"); + goto fail; + } + + /* read Command (some kind of multiple TLVs, this seems ok) */ + { + uint32_t offset = Track_Command_offset; + uint32_t max_offset = Track_Command_offset + Track_Command_size; + uint16_t tlv_code, tlv_type, tlv_index; + uint8_t tlv_size; + + + while (offset < max_offset) { + tlv_code = read_u16be(offset + 0x00, acb->TrackCommandSf); + tlv_size = read_u8 (offset + 0x02, acb->TrackCommandSf); + offset += 0x03; + + if (tlv_code == 0x07D0) { + if (tlv_size < 0x04) { + VGM_LOG("ACB: TLV with unknown size\n"); + break; + } + + tlv_type = read_u16be(offset + 0x00, acb->TrackCommandSf); + tlv_index = read_u16be(offset + 0x02, acb->TrackCommandSf); + //;VGM_LOG("ACB: TLV at %x: type %x, index=%x\n", offset, tlv_type, tlv_index); + + /* probably same as Synth_ReferenceItem_type */ + switch(tlv_type) { + + case 0x02: /* Synth (common) */ + if (!load_acb_synth(acb, tlv_index)) + goto fail; + break; + + case 0x03: /* Sequence of Synths (common, ex. Yakuza 6, Yakuza Kiwami 2) */ + if (!load_acb_sequence(acb, tlv_index)) + goto fail; + break; + + /* possible values? (from debug): + * - sequence + * - track + * - synth + * - trackEvent + * - seqParameterPallet, + * - trackParameterPallet, + * - synthParameterPallet, + */ + default: + VGM_LOG("ACB: unknown TLV type %x at %x + %x\n", tlv_type, offset, tlv_size); + max_offset = 0; /* force end without failing */ + break; + } + } + + /* 0x07D1 comes suspiciously often paired with 0x07D0 too */ + + offset += tlv_size; + } + } + + return 1; +fail: + return 0; +} + +static int load_acb_sequence(acb_header* acb, int16_t Index) { + int i; + uint16_t Sequence_NumTracks; + uint32_t Sequence_TrackIndex_offset; + uint32_t Sequence_TrackIndex_size; + + + /* read Sequence[Index] */ + if (!open_utf_subtable(acb, &acb->SequenceSf, &acb->SequenceTable, "SequenceTable", NULL, ACB_TABLE_BUFFER_SEQUENCE)) + goto fail; + if (!utf_query_u16(acb->SequenceTable, Index, "NumTracks", &Sequence_NumTracks)) + goto fail; + if (!utf_query_data(acb->SequenceTable, Index, "TrackIndex", &Sequence_TrackIndex_offset, &Sequence_TrackIndex_size)) + goto fail; + //;VGM_LOG("ACB: Sequence[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, Sequence_NumTracks, Sequence_TrackIndex_offset,Sequence_TrackIndex_size); + + acb->sequence_depth++; + + if (acb->sequence_depth > 3) { + VGM_LOG("ACB: Sequence depth too high\n"); + goto fail; /* max Sequence > Sequence > Sequence > Synth > Waveform (ex. Yakuza 6) */ + } + + if (Sequence_NumTracks * 0x02 > Sequence_TrackIndex_size) { /* padding may exist */ + VGM_LOG("ACB: wrong Sequence.TrackIndex size\n"); + goto fail; + } + + /* read Tracks inside Sequence */ + for (i = 0; i < Sequence_NumTracks; i++) { + int16_t Sequence_TrackIndex_index = read_s16be(Sequence_TrackIndex_offset + i*0x02, acb->SequenceSf); + + if (!load_acb_track_event_command(acb, Sequence_TrackIndex_index)) + goto fail; + } + + acb->sequence_depth--; + + return 1; +fail: + return 0; +} + +static int load_acb_block(acb_header* acb, int16_t Index) { + int i; + uint16_t Block_NumTracks; + uint32_t Block_TrackIndex_offset; + uint32_t Block_TrackIndex_size; + + + /* read Block[Index] */ + if (!open_utf_subtable(acb, &acb->BlockSf, &acb->BlockTable, "BlockTable", NULL, ACB_TABLE_BUFFER_BLOCK)) + goto fail; + if (!utf_query_u16(acb->BlockTable, Index, "NumTracks", &Block_NumTracks)) + goto fail; + if (!utf_query_data(acb->BlockTable, Index, "TrackIndex", &Block_TrackIndex_offset, &Block_TrackIndex_size)) + goto fail; + //;VGM_LOG("ACB: Block[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, Block_NumTracks, Block_TrackIndex_offset,Block_TrackIndex_size); + + if (Block_NumTracks * 0x02 > Block_TrackIndex_size) { /* padding may exist */ + VGM_LOG("ACB: wrong Block.TrackIndex size\n"); + goto fail; + } + + /* read Tracks inside Block */ + for (i = 0; i < Block_NumTracks; i++) { + int16_t Block_TrackIndex_index = read_s16be(Block_TrackIndex_offset + i*0x02, acb->BlockSf); + + if (!load_acb_track_event_command(acb, Block_TrackIndex_index)) + goto fail; + } + + return 1; +fail: + return 0; + +} + +static int load_acb_cue(acb_header* acb, int16_t Index) { + uint8_t Cue_ReferenceType; + uint16_t Cue_ReferenceIndex; + + + /* read Cue[Index] */ + if (!open_utf_subtable(acb, &acb->CueSf, &acb->CueTable, "CueTable", NULL, ACB_TABLE_BUFFER_CUE)) + goto fail; + if (!utf_query_u8(acb->CueTable, Index, "ReferenceType", &Cue_ReferenceType)) + goto fail; + if (!utf_query_u16(acb->CueTable, Index, "ReferenceIndex", &Cue_ReferenceIndex)) + goto fail; + //;VGM_LOG("ACB: Cue[%i]: ReferenceType=%i, ReferenceIndex=%i\n", Index, Cue_ReferenceType, Cue_ReferenceIndex); + + + /* usually older games use older references but not necessarily */ + switch(Cue_ReferenceType) { + + case 0x01: /* Cue > Waveform (ex. PES 2015) */ + if (!load_acb_waveform(acb, Cue_ReferenceIndex)) + goto fail; + break; + + case 0x02: /* Cue > Synth > Waveform (ex. Ukiyo no Roushi) */ + if (!load_acb_synth(acb, Cue_ReferenceIndex)) + goto fail; + break; + + case 0x03: /* Cue > Sequence > Track > Command > Synth > Waveform (ex. Valkyrie Profile anatomia, Yakuza Kiwami 2) */ + if (!load_acb_sequence(acb, Cue_ReferenceIndex)) + goto fail; + break; + + //todo "blockSequence"? + case 0x08: /* Cue > Block > Track > Command > Synth > Waveform (ex. Sonic Lost World, rare) */ + if (!load_acb_block(acb, Cue_ReferenceIndex)) + goto fail; + break; + + case 0x00: /* none */ + case 0x04: /* "track" */ + case 0x05: /* "outsideLink" */ + case 0x06: /* "insideLinkSynth" (ex. PES 2014) */ + case 0x07: /* "insideLinkSequence" (ex. PES 2014) */ + case 0x09: /* "insideLinkBlockSequence" */ + case 0x0a: /* "eventCue_UnUse" */ + case 0x0b: /* "soundGenerator" */ + default: + VGM_LOG("ACB: unknown Cue.ReferenceType=%x, Cue.ReferenceIndex=%x\n", Cue_ReferenceType, Cue_ReferenceIndex); + break; /* ignore and continue */ + } + + + return 1; +fail: + return 0; + +} + +static int load_acb_cuename(acb_header* acb, int16_t Index) { + uint16_t CueName_CueIndex; + const char* CueName_CueName; + + + /* read CueName[Index] */ + if (!open_utf_subtable(acb, &acb->CueNameSf, &acb->CueNameTable, "CueNameTable", NULL, ACB_TABLE_BUFFER_CUENAME)) + goto fail; + if (!utf_query_u16(acb->CueNameTable, Index, "CueIndex", &CueName_CueIndex)) + goto fail; + if (!utf_query_string(acb->CueNameTable, Index, "CueName", &CueName_CueName)) + goto fail; + //;VGM_LOG("ACB: CueName[%i]: CueIndex=%i, CueName=%s\n", Index, CueName_CueIndex, CueName_CueName); + + + /* save as will be needed if references waveform */ + acb->cuename_index = Index; + acb->cuename_name = CueName_CueName; + + if (!load_acb_cue(acb, CueName_CueIndex)) + goto fail; + + return 1; +fail: + return 0; +} + + +void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int is_memory) { + acb_header acb = {0}; + int i, CueName_rows; + + + if (!sf || !vgmstream || waveid < 0) + return; + + /* Normally games load a .acb + .awb, and asks the .acb to play a cue by name or index. + * Since we only care for actual waves, to get its name we need to find which cue uses our wave. + * Multiple cues can use the same wave (meaning multiple names), and one cue may use multiple waves. + * There is no easy way to map cue name <> wave name so basically we parse the whole thing. + * + * .acb are created in CRI Atom Craft, where user defines N Cues with CueName each, then link somehow + * to a Waveform (.awb=streamed or memory .acb=internal, data 'material' encoded in some format), + * depending on reference types. Typical links are: + * - CueName > Cue > Waveform (type 1) + * - CueName > Cue > Synth > Waveform (type 2) + * - CueName > Cue > Sequence > Track > Command > Synth > Waveform (type 3, <=v1.27) + * - CueName > Cue > Sequence > Track > Command > Synth > Synth > Waveform (type 3, <=v1.27) + * - CueName > Cue > Sequence > Track > TrackEvent > Command > Synth > Waveform (type 3, >=v1.28) + * - CueName > Cue > Sequence > Track > TrackEvent > Command > Synth > Synth > Waveform (type 3, >=v1.28) + * - CueName > Cue > Sequence > Track > TrackEvent > Command > Sequence > (...) > Synth > Waveform (type 3, >=v1.28) + * - CueName > Cue > Block > Track > Command > Synth > Synth > Waveform (type 8) + * - others should be possible but haven't been observed + * Atom Craft may only target certain .acb versions so some links are later removed + * Not all cues to point to though Waveforms, as some are just config events/commands. + * .acb link to .awb by name (loaded manually), though they have a checksum/hash to validate. + */ + + //;VGM_LOG("ACB: find waveid=%i\n", waveid); + + acb.acbFile = sf; + + acb.Header = utf_open(acb.acbFile, 0x00, NULL, NULL); + if (!acb.Header) goto fail; + + acb.target_waveid = waveid; + acb.is_memory = is_memory; + acb.has_TrackEventTable = utf_query_data(acb.Header, 0, "TrackEventTable", NULL,NULL); + acb.has_CommandTable = utf_query_data(acb.Header, 0, "CommandTable", NULL,NULL); + + + /* read all possible cue names and find which waveids are referenced by it */ + if (!open_utf_subtable(&acb, &acb.CueNameSf, &acb.CueNameTable, "CueNameTable", &CueName_rows, ACB_TABLE_BUFFER_CUENAME)) + goto fail; + for (i = 0; i < CueName_rows; i++) { + + if (!load_acb_cuename(&acb, i)) + goto fail; + } + + /* meh copy */ + if (acb.awbname_count > 0) { + strncpy(vgmstream->stream_name, acb.name, STREAM_NAME_SIZE); + vgmstream->stream_name[STREAM_NAME_SIZE - 1] = '\0'; + } + +fail: + utf_close(acb.Header); + + utf_close(acb.CueNameTable); + utf_close(acb.CueTable); + utf_close(acb.SequenceTable); + utf_close(acb.TrackTable); + utf_close(acb.TrackCommandTable); + utf_close(acb.SynthTable); + utf_close(acb.WaveformTable); + + close_streamfile(acb.CueNameSf); + close_streamfile(acb.CueSf); + close_streamfile(acb.SequenceSf); + close_streamfile(acb.TrackSf); + close_streamfile(acb.TrackCommandSf); + close_streamfile(acb.SynthSf); + close_streamfile(acb.WaveformSf); +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/aix.c b/Frameworks/vgmstream/vgmstream/src/meta/aix.c index 41ce11454..b11d39060 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/aix.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/aix.c @@ -3,13 +3,15 @@ #include "aix_streamfile.h" -#define MAX_SEGMENTS 50 /* usually segment0=intro, segment1=loop/main, sometimes ~5, rarely ~40 */ +/* usually segment0=intro, segment1=loop/main, sometimes ~5, rarely ~40~115 + * as pseudo dynamic/multi-song container [Sega Ages 2500 Vol 28 Tetris Collection (PS2)] */ +#define MAX_SEGMENTS 120 -static VGMSTREAM *build_segmented_vgmstream(STREAMFILE *streamFile, off_t *segment_offsets, size_t *segment_sizes, int32_t *segment_samples, int segment_count, int layer_count); +static VGMSTREAM* build_segmented_vgmstream(STREAMFILE* sf, off_t* segment_offsets, size_t* segment_sizes, int32_t* segment_samples, int segment_count, int layer_count); /* AIX - N segments with M layers (2ch ADX) inside [SoulCalibur IV (PS3), Dragon Ball Z: Burst Limit (PS3)] */ -VGMSTREAM * init_vgmstream_aix(STREAMFILE *sf) { - VGMSTREAM * vgmstream = NULL; +VGMSTREAM* init_vgmstream_aix(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; off_t segment_offsets[MAX_SEGMENTS] = {0}; size_t segment_sizes[MAX_SEGMENTS] = {0}; @@ -106,8 +108,8 @@ fail: return NULL; } -static VGMSTREAM *build_layered_vgmstream(STREAMFILE *streamFile, off_t segment_offset, size_t segment_size, int layer_count) { - VGMSTREAM *vgmstream = NULL; +static VGMSTREAM *build_layered_vgmstream(STREAMFILE* sf, off_t segment_offset, size_t segment_size, int layer_count) { + VGMSTREAM* vgmstream = NULL; layered_layout_data* data = NULL; int i; STREAMFILE* temp_sf = NULL; @@ -119,7 +121,7 @@ static VGMSTREAM *build_layered_vgmstream(STREAMFILE *streamFile, off_t segment_ for (i = 0; i < layer_count; i++) { /* build the layer STREAMFILE */ - temp_sf = setup_aix_streamfile(streamFile, segment_offset, segment_size, i, "adx"); + temp_sf = setup_aix_streamfile(sf, segment_offset, segment_size, i, "adx"); if (!temp_sf) goto fail; /* build the sub-VGMSTREAM */ @@ -149,9 +151,9 @@ fail: return NULL; } -static VGMSTREAM *build_segmented_vgmstream(STREAMFILE *streamFile, off_t *segment_offsets, size_t *segment_sizes, int32_t *segment_samples, int segment_count, int layer_count) { - VGMSTREAM *vgmstream = NULL; - segmented_layout_data *data = NULL; +static VGMSTREAM *build_segmented_vgmstream(STREAMFILE* sf, off_t* segment_offsets, size_t* segment_sizes, int32_t* segment_samples, int segment_count, int layer_count) { + VGMSTREAM* vgmstream = NULL; + segmented_layout_data* data = NULL; int i, loop_flag, loop_start_segment, loop_end_segment; @@ -161,7 +163,7 @@ static VGMSTREAM *build_segmented_vgmstream(STREAMFILE *streamFile, off_t *segme for (i = 0; i < segment_count; i++) { /* build the layered sub-VGMSTREAM */ - data->segments[i] = build_layered_vgmstream(streamFile, segment_offsets[i], segment_sizes[i], layer_count); + data->segments[i] = build_layered_vgmstream(sf, segment_offsets[i], segment_sizes[i], layer_count); if (!data->segments[i]) goto fail; data->segments[i]->num_samples = segment_samples[i]; /* just in case */ @@ -177,7 +179,7 @@ static VGMSTREAM *build_segmented_vgmstream(STREAMFILE *streamFile, off_t *segme * - 2 segments: intro + loop [SoulCalibur IV (PS3)] * - 3 segments: intro + loop + end [Dragon Ball Z: Burst Limit (PS3), Metroid: Other M (Wii)] * - 4/5 segments: intros + loop + ends [Danball Senki (PSP)] - * - 39 segments: no loops but multiple segments for dynamic parts? [Tetris Collection (PS2)] */ + * - +39 segments: no loops but multiple segments for dynamic parts? [Tetris Collection (PS2)] */ loop_flag = (segment_count > 0 && segment_count <= 5); loop_start_segment = (segment_count > 3) ? 2 : 1; loop_end_segment = (segment_count > 3) ? (segment_count - 2) : 1; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/bnsf_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/bnsf_keys.h index b415402c8..9ce44ea52 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/bnsf_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/bnsf_keys.h @@ -5,7 +5,11 @@ typedef struct { const char* key; } bnsfkey_info; -/* Known keys, extracted from games' exe/files */ +/* Known keys, from games' exe (iM@S, near "nus" strings) or files (Tales, config in audio bigfiles). + * + * In memdumps, first 16 chars of key can be found XORed with "Ua#oK3P94vdxX,ft" after AES 'Td' + * mix tables (that end with 8D4697A3 A38D4697 97A38D46 4697A38D), then can be cross referenced + * with other strings (max 24 chars) in the memdump. */ static const bnsfkey_info s14key_list[] = { /* THE iDOLM@STER 2 (PS3/X360) */ @@ -17,6 +21,9 @@ static const bnsfkey_info s14key_list[] = { /* Tales of Berseria (PS3) */ {"SPSLOC13"}, + /* THE iDOLM@STER: One For All (PS3) */ + {"86c215d7655eefb5c77ae92c"}, + }; -#endif/*_BNSF_KEYS_H_*/ +#endif /*_BNSF_KEYS_H_*/ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/deblock_streamfile.c b/Frameworks/vgmstream/vgmstream/src/meta/deblock_streamfile.c index 9ca3ce049..5b8ec2aee 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/deblock_streamfile.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/deblock_streamfile.c @@ -2,7 +2,7 @@ //todo move to utils or something -static void block_callback_default(STREAMFILE *sf, deblock_io_data *data) { +static void block_callback_default(STREAMFILE* sf, 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; @@ -10,7 +10,7 @@ static void block_callback_default(STREAMFILE *sf, deblock_io_data *data) { //;VGM_LOG("DEBLOCK: of=%lx, bs=%lx, ss=%lx, ds=%lx\n", data->physical_offset, data->block_size, data->skip_size, data->data_size); } -static size_t deblock_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, deblock_io_data* data) { +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; //;VGM_LOG("DEBLOCK: of=%lx, sz=%x, po=%lx\n", offset, length, data->physical_offset); @@ -25,9 +25,7 @@ static size_t deblock_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_ data->skip_size = 0; data->step_count = data->cfg.step_start; -/* - data->read_count = data->cfg.read_count; -*/ + //data->read_count = data->cfg.read_count; } /* read blocks */ @@ -98,6 +96,10 @@ static size_t deblock_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_ to_read = length; bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, sf); + if (data->cfg.read_callback) { + data->cfg.read_callback(dest, data, bytes_consumed, bytes_done); + } + total_read += bytes_done; dest += bytes_done; offset += bytes_done; @@ -112,7 +114,7 @@ static size_t deblock_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_ return total_read; } -static size_t deblock_io_size(STREAMFILE *streamfile, deblock_io_data* data) { +static size_t deblock_io_size(STREAMFILE* sf, deblock_io_data* data) { uint8_t buf[0x04]; if (data->logical_size) @@ -124,7 +126,7 @@ static size_t deblock_io_size(STREAMFILE *streamfile, deblock_io_data* data) { } /* 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); + deblock_io_read(sf, buf, 0x7FFFFFFF, 1, data); data->logical_size = data->logical_offset; //todo tests: @@ -141,8 +143,8 @@ static size_t deblock_io_size(STREAMFILE *streamfile, deblock_io_data* data) { * decoder can't easily use blocked layout, or some other weird feature. It "filters" data so * reader only sees clean data without blocks. Must pass setup config and a callback that sets * sizes of a single block. */ -STREAMFILE* open_io_deblock_streamfile_f(STREAMFILE *sf, deblock_config_t *cfg) { - STREAMFILE *new_sf = NULL; +STREAMFILE* open_io_deblock_streamfile_f(STREAMFILE* sf, deblock_config_t *cfg) { + STREAMFILE* new_sf = NULL; deblock_io_data io_data = {0}; /* prepare data */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/deblock_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/deblock_streamfile.h index e54039d96..ba6cc81d0 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/deblock_streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/deblock_streamfile.h @@ -34,7 +34,9 @@ struct deblock_config_t { size_t interleave_last_count; /* callback that setups deblock_io_data state, normally block_size and data_size */ - void (*block_callback)(STREAMFILE *sf, deblock_io_data *data); + void (*block_callback)(STREAMFILE* sf, deblock_io_data* data); + /* callback that alters block, with the current position into the block (0=beginning) */ + void (*read_callback)(uint8_t* dst, deblock_io_data* data, size_t block_pos, size_t read_size); } ; struct deblock_io_data { @@ -56,6 +58,6 @@ struct deblock_io_data { off_t physical_end; }; -STREAMFILE* open_io_deblock_streamfile_f(STREAMFILE *sf, deblock_config_t *cfg); +STREAMFILE* open_io_deblock_streamfile_f(STREAMFILE* sf, deblock_config_t* cfg); #endif /* _DEBLOCK_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c index 6d63aae7f..5dbccfe03 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c @@ -503,6 +503,9 @@ fail: static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track, int num_tracks) { static const char *const mapfile_pairs[][2] = { /* standard cases, replace map part with mus part (from the end to preserve prefixes) */ + {"game.mpf", "Game_Stream.mus"}, /* Skate */ + {"ipod.mpf", "Ipod_Stream.mus"}, + {"world.mpf", "World_Stream.mus"}, {"FreSkate.mpf", "track.mus,ram.mus"}, /* Skate It */ {"nsf_sing.mpf", "track_main.mus"}, /* Need for Speed: Nitro */ {"nsf_wii.mpf", "Track.mus"}, /* Need for Speed: Nitro */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c index d9bd9cc39..96a0081ee 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c @@ -264,7 +264,6 @@ fail: /* EA BNK with variable header - from EA games SFXs; also created by sx.exe */ VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE* sf) { - off_t offset; int target_stream = sf->stream_index; /* check extension */ @@ -276,15 +275,8 @@ VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE* sf) { if (!check_extensions(sf,"bnk,sdt,mus,abk,ast")) goto fail; - /* check header (doesn't use EA blocks, otherwise very similar to SCHl) */ - if (read_32bitBE(0x100,sf) == EA_BNK_HEADER_LE) - offset = 0x100; /* Harry Potter and the Goblet of Fire (PS2) .mus have weird extra 0x100 bytes */ - else - offset = 0x00; - if (target_stream == 0) target_stream = 1; - - return parse_bnk_header(sf, offset, target_stream - 1, 0); + return parse_bnk_header(sf, 0x00, target_stream - 1, 0); fail: return NULL; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c b/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c index 25552e369..a0b60e7fb 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c @@ -3,34 +3,28 @@ #ifdef VGM_USE_FFMPEG -static int read_pos_file(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile); +static int read_pos_file(uint8_t* buf, size_t bufsize, STREAMFILE* sf); +static int find_ogg_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end); -/** - * Generic init FFmpeg and vgmstream for any file supported by FFmpeg. - * Called by vgmstream when trying to identify the file type (if the player allows it). - */ -VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile) { - return init_vgmstream_ffmpeg_offset( streamFile, 0, streamFile->get_size(streamFile) ); -} - -VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) { - VGMSTREAM *vgmstream = NULL; - ffmpeg_codec_data *data = NULL; +/* parses any file supported by FFmpeg and not handled elsewhere (mainly: MP4/AAC, MP3, MPC, FLAC) */ +VGMSTREAM* init_vgmstream_ffmpeg(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + ffmpeg_codec_data* data = NULL; int loop_flag = 0; int32_t loop_start = 0, loop_end = 0, num_samples = 0; - int total_subsongs, target_subsong = streamFile->stream_index; + int total_subsongs, target_subsong = sf->stream_index; /* no checks */ - //if (!check_extensions(streamFile, "...")) + //if (!check_extensions(sf, "...")) // goto fail; /* don't try to open headers and other mini files */ - if (get_streamfile_size(streamFile) <= 0x1000) + if (get_streamfile_size(sf) <= 0x1000) goto fail; /* init ffmpeg */ - data = init_ffmpeg_offset(streamFile, start, size); + data = init_ffmpeg_offset(sf, 0, get_streamfile_size(sf)); if (!data) return NULL; total_subsongs = data->streamCount; @@ -41,38 +35,43 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, { uint8_t posbuf[4+4+4]; - if ( read_pos_file(posbuf, 4+4+4, streamFile) ) { - loop_start = get_32bitLE(posbuf+0); - loop_end = get_32bitLE(posbuf+4); + if (read_pos_file(posbuf, 4+4+4, sf)) { + loop_start = get_s32le(posbuf+0); + loop_end = get_s32le(posbuf+4); loop_flag = 1; /* incorrect looping will be validated outside */ /* FFmpeg can't always determine totalSamples correctly so optionally load it (can be 0/NULL) * won't crash and will output silence if no loop points and bigger than actual stream's samples */ - num_samples = get_32bitLE(posbuf+8); + num_samples = get_s32le(posbuf+8); } } + /* try to read Ogg loop tags (abridged) */ + if (loop_flag == 0 && read_u32be(0x00, sf) == 0x4F676753) { /* "OggS" */ + loop_flag = find_ogg_loops(data, &loop_start, &loop_end); + } + /* hack for AAC files (will return 0 samples if not an actual file) */ - if (!num_samples && check_extensions(streamFile, "aac,laac")) { - num_samples = aac_get_samples(streamFile, 0x00, get_streamfile_size(streamFile)); + if (!num_samples && check_extensions(sf, "aac,laac")) { + num_samples = aac_get_samples(sf, 0x00, get_streamfile_size(sf)); } #ifdef VGM_USE_MPEG /* hack for MP3 files (will return 0 samples if not an actual file) * .mus: Marc Ecko's Getting Up (PC) */ - if (!num_samples && check_extensions(streamFile, "mp3,lmp3,mus")) { - num_samples = mpeg_get_samples(streamFile, 0x00, get_streamfile_size(streamFile)); + if (!num_samples && check_extensions(sf, "mp3,lmp3,mus")) { + num_samples = mpeg_get_samples(sf, 0x00, get_streamfile_size(sf)); } #endif /* hack for MPC, that seeks/resets incorrectly due to seek table shenanigans */ - if (read_32bitBE(0x00, streamFile) == 0x4D502B07 || /* "MP+\7" (Musepack V7) */ - read_32bitBE(0x00, streamFile) == 0x4D50434B) { /* "MPCK" (Musepack V8) */ + if (read_u32be(0x00, sf) == 0x4D502B07 || /* "MP+\7" (Musepack V7) */ + read_u32be(0x00, sf) == 0x4D50434B) { /* "MPCK" (Musepack V8) */ ffmpeg_set_force_seek(data); } /* default but often inaccurate when calculated using bitrate (wrong for VBR) */ if (!num_samples) { - num_samples = data->totalSamples; + num_samples = data->totalSamples; /* may be 0 if FFmpeg can't precalculate it */ } @@ -87,15 +86,8 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, vgmstream->layout_type = layout_none; vgmstream->num_samples = num_samples; - if (loop_flag) { - vgmstream->loop_start_sample = loop_start; - vgmstream->loop_end_sample = loop_end; - } - - /* this may happen for some streams if FFmpeg can't determine it (ex. AAC) */ - if (vgmstream->num_samples <= 0) - goto fail; - + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data); return vgmstream; @@ -111,46 +103,94 @@ fail: } -/** - * open file containing looping data and copy to buffer - * - * returns true if found and copied - */ -int read_pos_file(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile) { +/* open file containing looping data and copy to buffer, returns true if found and copied */ +int read_pos_file(uint8_t* buf, size_t bufsize, STREAMFILE* sf) { char posname[PATH_LIMIT]; char filename[PATH_LIMIT]; /*size_t bytes_read;*/ - STREAMFILE * streamFilePos= NULL; + STREAMFILE* sf_pos = NULL; - streamFile->get_name(streamFile,filename,sizeof(filename)); + get_streamfile_name(sf,filename,sizeof(filename)); - if (strlen(filename)+4 > sizeof(posname)) goto fail; + if (strlen(filename)+4 > sizeof(posname)) + goto fail; /* try to open a posfile using variations: "(name.ext).pos" */ { strcpy(posname, filename); strcat(posname, ".pos"); - streamFilePos = streamFile->open(streamFile,posname,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (streamFilePos) goto found; + sf_pos = open_streamfile(sf, posname);; + if (sf_pos) goto found; goto fail; } found: - //if (get_streamfile_size(streamFilePos) != bufsize) goto fail; + //if (get_streamfile_size(sf_pos) != bufsize) goto fail; /* allow pos files to be of different sizes in case of new features, just fill all we can */ memset(buf, 0, bufsize); - read_streamfile(buf, 0, bufsize, streamFilePos); + read_streamfile(buf, 0, bufsize, sf_pos); - close_streamfile(streamFilePos); + close_streamfile(sf_pos); return 1; fail: - if (streamFilePos) close_streamfile(streamFilePos); - + close_streamfile(sf_pos); return 0; } + +/* loop tag handling could be unified with ogg_vorbis.c, but that one has a extra features too */ +static int find_ogg_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end) { + char* endptr; + const char* value; + int loop_flag = 0; + int32_t loop_start = -1, loop_end = -1; + + // Try to detect the loop flags based on current file metadata + value = ffmpeg_get_metadata_value(data, "LoopStart"); + if (value != NULL) { + loop_start = strtol(value, &endptr, 10); + loop_flag = 1; + } + + value = ffmpeg_get_metadata_value(data, "LoopEnd"); + if (value != NULL) { + loop_end = strtol(value, &endptr, 10); + loop_flag = 1; + } + + if (loop_flag) { + if (loop_end <= 0) { + // Detected a loop, but loop_end is still undefined or wrong. Try to calculate it. + value = ffmpeg_get_metadata_value(data, "LoopLength"); + if (value != NULL) { + int loop_length = strtol(value, &endptr, 10); + + if (loop_start != -1) loop_end = loop_start + loop_length; + } + } + + if (loop_end <= 0) { + // Looks a calculation was not possible, or tag value is wrongly set. Use the end of track as end value + loop_end = data->totalSamples; + } + + if (loop_start <= 0) { + // Weird edge case: loopend is defined and there's a loop, but loopstart was never defined. Reset to sane value + loop_start = 0; + } + } else { + // Every other attempt to detect loop information failed, reset start/end flags to sane values + loop_start = 0; + loop_end = 0; + } + + *p_loop_start = loop_start; + *p_loop_end = loop_end; + return loop_flag; +} + #endif diff --git a/Frameworks/vgmstream/vgmstream/src/meta/fsb.c b/Frameworks/vgmstream/vgmstream/src/meta/fsb.c index 730e896e2..ae6fcc643 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/fsb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/fsb.c @@ -1,600 +1,606 @@ -#include "meta.h" -#include "../coding/coding.h" -#include "../layout/layout.h" -#include "fsb_interleave_streamfile.h" - - -/* ************************************************************************************************ - * FSB defines, copied from the public spec (https://www.fmod.org/questions/question/forum-4928/) - * for reference. The format is mostly compatible for FSB1/2/3/4, but not FSB5. - * ************************************************************************************************ */ -/* These flags are used for FMOD_FSB_HEADER::mode */ -#define FMOD_FSB_SOURCE_FORMAT 0x00000001 /* all samples stored in their original compressed format */ -#define FMOD_FSB_SOURCE_BASICHEADERS 0x00000002 /* samples should use the basic header structure */ -#define FMOD_FSB_SOURCE_ENCRYPTED 0x00000004 /* all sample data is encrypted */ -#define FMOD_FSB_SOURCE_BIGENDIANPCM 0x00000008 /* pcm samples have been written out in big-endian format */ -#define FMOD_FSB_SOURCE_NOTINTERLEAVED 0x00000010 /* Sample data is not interleaved. */ -#define FMOD_FSB_SOURCE_MPEG_PADDED 0x00000020 /* Mpeg frames are now rounded up to the nearest 2 bytes for normal sounds, or 16 bytes for multichannel. */ -#define FMOD_FSB_SOURCE_MPEG_PADDED4 0x00000040 /* Mpeg frames are now rounded up to the nearest 4 bytes for normal sounds, or 16 bytes for multichannel. */ - -/* These flags are used for FMOD_FSB_HEADER::version */ -#define FMOD_FSB_VERSION_3_0 0x00030000 /* FSB version 3.0 */ -#define FMOD_FSB_VERSION_3_1 0x00030001 /* FSB version 3.1 */ -#define FMOD_FSB_VERSION_4_0 0x00040000 /* FSB version 4.0 */ - -/* FMOD 3 defines. These flags are used for FMOD_FSB_SAMPLE_HEADER::mode */ -#define FSOUND_LOOP_OFF 0x00000001 /* For non looping samples. */ -#define FSOUND_LOOP_NORMAL 0x00000002 /* For forward looping samples. */ -#define FSOUND_LOOP_BIDI 0x00000004 /* For bidirectional looping samples. (no effect if in hardware). */ -#define FSOUND_8BITS 0x00000008 /* For 8 bit samples. */ -#define FSOUND_16BITS 0x00000010 /* For 16 bit samples. */ -#define FSOUND_MONO 0x00000020 /* For mono samples. */ -#define FSOUND_STEREO 0x00000040 /* For stereo samples. */ -#define FSOUND_UNSIGNED 0x00000080 /* For user created source data containing unsigned samples. */ -#define FSOUND_SIGNED 0x00000100 /* For user created source data containing signed data. */ -#define FSOUND_MPEG 0x00000200 /* For MPEG layer 2/3 data. */ -#define FSOUND_CHANNELMODE_ALLMONO 0x00000400 /* Sample is a collection of mono channels. */ -#define FSOUND_CHANNELMODE_ALLSTEREO 0x00000800 /* Sample is a collection of stereo channel pairs */ -#define FSOUND_HW3D 0x00001000 /* Attempts to make samples use 3d hardware acceleration. (if the card supports it) */ -#define FSOUND_2D 0x00002000 /* Tells software (not hardware) based sample not to be included in 3d processing. */ -#define FSOUND_SYNCPOINTS_NONAMES 0x00004000 /* Specifies that syncpoints are present with no names */ -#define FSOUND_DUPLICATE 0x00008000 /* This subsound is a duplicate of the previous one i.e. it uses the same sample data but w/different mode bits */ -#define FSOUND_CHANNELMODE_PROTOOLS 0x00010000 /* Sample is 6ch and uses L C R LS RS LFE standard. */ -#define FSOUND_MPEGACCURATE 0x00020000 /* For FSOUND_Stream_Open - for accurate FSOUND_Stream_GetLengthMs/FSOUND_Stream_SetTime. WARNING, see FSOUND_Stream_Open for inital opening time performance issues. */ -#define FSOUND_HW2D 0x00080000 /* 2D hardware sounds. allows hardware specific effects */ -#define FSOUND_3D 0x00100000 /* 3D software sounds */ -#define FSOUND_32BITS 0x00200000 /* For 32 bit (float) samples. */ -#define FSOUND_IMAADPCM 0x00400000 /* Contents are stored compressed as IMA ADPCM */ -#define FSOUND_VAG 0x00800000 /* For PS2 only - Contents are compressed as Sony VAG format */ -#define FSOUND_XMA 0x01000000 /* For Xbox360 only - Contents are compressed as XMA format */ -#define FSOUND_GCADPCM 0x02000000 /* For Gamecube only - Contents are compressed as Gamecube DSP-ADPCM format */ -#define FSOUND_MULTICHANNEL 0x04000000 /* For PS2 and Gamecube only - Contents are interleaved into a multi-channel (more than stereo) format */ -#define FSOUND_OGG 0x08000000 /* For vorbis encoded ogg data */ -#define FSOUND_CELT 0x08000000 /* For vorbis encoded ogg data */ -#define FSOUND_MPEG_LAYER3 0x10000000 /* Data is in MP3 format. */ -#define FSOUND_MPEG_LAYER2 0x00040000 /* Data is in MP2 format. */ -#define FSOUND_LOADMEMORYIOP 0x20000000 /* For PS2 only - "name" will be interpreted as a pointer to data for streaming and samples. The address provided will be an IOP address */ -#define FSOUND_IMAADPCMSTEREO 0x20000000 /* Signify IMA ADPCM is actually stereo not two interleaved mono */ -#define FSOUND_IGNORETAGS 0x40000000 /* Skips id3v2 etc tag checks when opening a stream, to reduce seek/read overhead when opening files (helps with CD performance) */ -#define FSOUND_SYNCPOINTS 0x80000000 /* Specifies that syncpoints are present */ - -/* These flags are used for FMOD_FSB_SAMPLE_HEADER::mode */ -#define FSOUND_CHANNELMODE_MASK (FSOUND_CHANNELMODE_ALLMONO | FSOUND_CHANNELMODE_ALLSTEREO | FSOUND_CHANNELMODE_PROTOOLS) -#define FSOUND_CHANNELMODE_DEFAULT 0x00000000 /* Determine channel assignment automatically from channel count. */ -#define FSOUND_CHANNELMODE_RESERVED 0x00000C00 -#define FSOUND_NORMAL (FSOUND_16BITS | FSOUND_SIGNED | FSOUND_MONO) -#define FSB_SAMPLE_DATA_ALIGN 32 - - -/* simplified struct based on the original definitions */ -typedef enum { MPEG, IMA, PSX, XMA2, DSP, CELT, PCM8, PCM16 } fsb_codec_t; -typedef struct { - /* main header */ - uint32_t id; - int32_t total_subsongs; - uint32_t sample_headers_size; /* all of them including extended information */ - uint32_t sample_data_size; - uint32_t version; /* extended fsb version (in FSB 3/3.1/4) */ - uint32_t flags; /* flags common to all streams (in FSB 3/3.1/4)*/ - /* sample header */ - uint32_t num_samples; - uint32_t stream_size; - uint32_t loop_start; - uint32_t loop_end; - uint32_t mode; - int32_t sample_rate; - uint16_t channels; - /* extra */ - uint32_t base_header_size; - uint32_t sample_header_min; - off_t extradata_offset; - off_t first_extradata_offset; - - meta_t meta_type; - off_t name_offset; - size_t name_size; - - int loop_flag; - - off_t stream_offset; - - fsb_codec_t codec; -} fsb_header; - -/* ********************************************************************************** */ - -static layered_layout_data* build_layered_fsb_celt(STREAMFILE *streamFile, fsb_header* fsb, int is_new_lib); - -/* FSB1~4 - from games using FMOD audio middleware */ -VGMSTREAM * init_vgmstream_fsb(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - int target_subsong = streamFile->stream_index; - fsb_header fsb = {0}; - - - /* checks - * .fsb: standard - * .bnk: Hard Corps Uprising (PS3) */ - if ( !check_extensions(streamFile, "fsb,bnk") ) - goto fail; - - /* check header */ - fsb.id = read_32bitBE(0x00,streamFile); - if (fsb.id == 0x46534231) { /* "FSB1" (somewhat different from other fsbs) */ - fsb.meta_type = meta_FSB1; - fsb.base_header_size = 0x10; - fsb.sample_header_min = 0x40; - - /* main header */ - fsb.total_subsongs = read_32bitLE(0x04,streamFile); - fsb.sample_data_size = read_32bitLE(0x08,streamFile); - fsb.sample_headers_size = 0x40; - fsb.version = 0; - fsb.flags = 0; - - if (fsb.total_subsongs > 1) goto fail; - - /* sample header (first stream only, not sure if there are multi-FSB1) */ - { - off_t header_offset = fsb.base_header_size; - - fsb.name_offset = header_offset; - fsb.name_size = 0x20; - fsb.num_samples = read_32bitLE(header_offset+0x20,streamFile); - fsb.stream_size = read_32bitLE(header_offset+0x24,streamFile); - fsb.sample_rate = read_32bitLE(header_offset+0x28,streamFile); - /* 0x2c:? 0x2e:? 0x30:? 0x32:? */ - fsb.mode = read_32bitLE(header_offset+0x34,streamFile); - fsb.loop_start = read_32bitLE(header_offset+0x38,streamFile); - fsb.loop_end = read_32bitLE(header_offset+0x3c,streamFile); - - VGM_ASSERT(fsb.loop_end > fsb.num_samples, "FSB: loop end over samples (%i vs %i)\n", fsb.loop_end, fsb.num_samples); - fsb.channels = (fsb.mode & FSOUND_STEREO) ? 2 : 1; - if (fsb.loop_end > fsb.num_samples) /* this seems common... */ - fsb.num_samples = fsb.loop_end; - - /* DSP coefs, seek tables, etc */ - fsb.extradata_offset = header_offset+fsb.sample_header_min; - - fsb.stream_offset = fsb.base_header_size + fsb.sample_headers_size; - } - } - else { /* other FSBs (common/extended format) */ - if (fsb.id == 0x46534232) { /* "FSB2" */ - fsb.meta_type = meta_FSB2; - fsb.base_header_size = 0x10; - fsb.sample_header_min = 0x40; /* guessed */ - } else if (fsb.id == 0x46534233) { /* "FSB3" */ - fsb.meta_type = meta_FSB3; - fsb.base_header_size = 0x18; - fsb.sample_header_min = 0x40; - } else if (fsb.id == 0x46534234) { /* "FSB4" */ - fsb.meta_type = meta_FSB4; - fsb.base_header_size = 0x30; - fsb.sample_header_min = 0x50; - } else { - goto fail; - } - - /* main header */ - fsb.total_subsongs = read_32bitLE(0x04,streamFile); - fsb.sample_headers_size = read_32bitLE(0x08,streamFile); - fsb.sample_data_size = read_32bitLE(0x0c,streamFile); - if (fsb.base_header_size > 0x10) { - fsb.version = read_32bitLE(0x10,streamFile); - fsb.flags = read_32bitLE(0x14,streamFile); - /* FSB4: 0x18(8):hash 0x20(10):guid */ - } else { - fsb.version = 0; - fsb.flags = 0; - } - - if (fsb.version == FMOD_FSB_VERSION_3_1) { - fsb.sample_header_min = 0x50; - } else if (fsb.version != 0 /* FSB2 */ - && fsb.version != FMOD_FSB_VERSION_3_0 - && fsb.version != FMOD_FSB_VERSION_4_0) { - goto fail; - } - - if (fsb.sample_headers_size < fsb.sample_header_min) goto fail; - if (target_subsong == 0) target_subsong = 1; - if (target_subsong < 0 || target_subsong > fsb.total_subsongs || fsb.total_subsongs < 1) goto fail; - - /* sample header (N-stream) */ - { - int i; - off_t header_offset = fsb.base_header_size; - off_t data_offset = fsb.base_header_size + fsb.sample_headers_size; - - /* find target_stream header (variable sized) */ - for (i = 0; i < fsb.total_subsongs; i++) { - size_t stream_header_size; - - if ((fsb.flags & FMOD_FSB_SOURCE_BASICHEADERS) && i > 0) { - /* miniheader, all subsongs reuse first header [rare, ex. Biker Mice from Mars (PS2)] */ - stream_header_size = 0x08; - fsb.num_samples = read_32bitLE(header_offset+0x00,streamFile); - fsb.stream_size = read_32bitLE(header_offset+0x04,streamFile); - fsb.loop_start = 0; - fsb.loop_end = 0; - } - else { - /* subsong header for normal files */ - stream_header_size = (uint16_t)read_16bitLE(header_offset+0x00,streamFile); - fsb.name_offset = header_offset+0x02; - fsb.name_size = 0x20-0x02; - fsb.num_samples = read_32bitLE(header_offset+0x20,streamFile); - fsb.stream_size = read_32bitLE(header_offset+0x24,streamFile); - fsb.loop_start = read_32bitLE(header_offset+0x28,streamFile); - fsb.loop_end = read_32bitLE(header_offset+0x2c,streamFile); - fsb.mode = read_32bitLE(header_offset+0x30,streamFile); - fsb.sample_rate = read_32bitLE(header_offset+0x34,streamFile); - /* 0x38: defvol, 0x3a: defpan, 0x3c: defpri */ - fsb.channels = read_16bitLE(header_offset+0x3e,streamFile); - /* FSB3.1/4: - * 0x40: mindistance, 0x44: maxdistance, 0x48: varfreq/size_32bits - * 0x4c: varvol, 0x4e: fsb.varpan */ - - /* DSP coefs, seek tables, etc */ - if (stream_header_size > fsb.sample_header_min) { - fsb.extradata_offset = header_offset+fsb.sample_header_min; - if (fsb.first_extradata_offset == 0) - fsb.first_extradata_offset = fsb.extradata_offset; - } - } - - if (i+1 == target_subsong) /* final data_offset found */ - break; - - header_offset += stream_header_size; - data_offset += fsb.stream_size; /* there is no offset so manually count */ - - /* some subsongs offsets need padding (most FSOUND_IMAADPCM, few MPEG too [Hard Reset (PC) subsong 5]) - * other codecs may set PADDED4 (ex. XMA) but don't seem to need it and work fine */ - if (fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED4) { - if (data_offset % 0x20) - data_offset += 0x20 - (data_offset % 0x20); - } - } - if (i > fsb.total_subsongs) - goto fail; /* not found */ - - fsb.stream_offset = data_offset; - } - } - - - /* convert to clean some code */ - if (fsb.mode & FSOUND_MPEG) fsb.codec = MPEG; - else if (fsb.mode & FSOUND_IMAADPCM) fsb.codec = IMA; - else if (fsb.mode & FSOUND_VAG) fsb.codec = PSX; - else if (fsb.mode & FSOUND_XMA) fsb.codec = XMA2; - else if (fsb.mode & FSOUND_GCADPCM) fsb.codec = DSP; - else if (fsb.mode & FSOUND_CELT) fsb.codec = CELT; - else if (fsb.mode & FSOUND_8BITS) fsb.codec = PCM8; - else fsb.codec = PCM16; - - /* correct compared to FMOD's tools */ - if (fsb.loop_end) - fsb.loop_end += 1; - - /* ping-pong looping = no looping? (forward > reverse > forward) [ex. Biker Mice from Mars (PS2)] */ - VGM_ASSERT(fsb.mode & FSOUND_LOOP_BIDI, "FSB BIDI looping found\n"); - VGM_ASSERT(fsb.mode & FSOUND_LOOP_OFF, "FSB LOOP OFF found\n"); /* sometimes used */ - VGM_ASSERT(fsb.mode & FSOUND_LOOP_NORMAL, "FSB LOOP NORMAL found\n"); /* very rarely set */ - /* XOR encryption for some FSB4, though the flag is only seen after decrypting */ - //;VGM_ASSERT(fsb.flags & FMOD_FSB_SOURCE_ENCRYPTED, "FSB ENCRYPTED found\n"); - - /* sometimes there is garbage at the end or missing bytes due to improper ripping */ - VGM_ASSERT(fsb.base_header_size + fsb.sample_headers_size + fsb.sample_data_size != streamFile->get_size(streamFile), - "FSB wrong head/data_size found (expected 0x%x vs 0x%x)\n", - fsb.base_header_size + fsb.sample_headers_size + fsb.sample_data_size, streamFile->get_size(streamFile)); - - /* autodetect unwanted loops */ - { - /* FMOD tool's default behaviour is creating files with full loops and no flags unless disabled - * manually (can be overriden during program too), for all FSB versions. This makes jingles/sfx/voices - * loop when they shouldn't, but most music does full loops seamlessly, so we only want to disable - * if it looks jingly enough. Incidentally, their tools can only make files with full loops. */ - int enable_loop, full_loop, is_small; - - /* seems to mean forced loop */ - enable_loop = (fsb.mode & FSOUND_LOOP_NORMAL); - - /* for MPEG and CELT sometimes full loops are given with around/exact 1 frame less than num_samples, - * probably to account for encoder/decoder delay (ex. The Witcher 2, Hard Reset, Timeshift) */ - if (fsb.codec == CELT) - full_loop = fsb.loop_start - 512 <= 0 && fsb.loop_end >= fsb.num_samples - 512; /* aproximate */ - else if (fsb.codec == MPEG) - full_loop = fsb.loop_start - 1152 <= 0 && fsb.loop_end >= fsb.num_samples - 1152; /* WWF Legends of Wrestlemania uses 2 frames? */ - else - full_loop = fsb.loop_start == 0 && fsb.loop_end == fsb.num_samples; - - /* in seconds (lame but no better way) */ - is_small = fsb.num_samples < 20 * fsb.sample_rate; - - //;VGM_LOG("FSB: loop start=%i, loop end=%i, samples=%i, mode=%x\n", fsb.loop_start, fsb.loop_end, fsb.num_samples, fsb.mode); - //;VGM_LOG("FSB: enable=%i, full=%i, small=%i\n",enable_loop,full_loop,is_small ); - - fsb.loop_flag = !(fsb.mode & FSOUND_LOOP_OFF); /* disabled manually */ - if (fsb.loop_flag && !enable_loop && full_loop && is_small) { - VGM_LOG("FSB: disable unwanted loop\n"); - fsb.loop_flag = 0; - } - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(fsb.channels,fsb.loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = fsb.sample_rate; - vgmstream->num_samples = fsb.num_samples; - vgmstream->loop_start_sample = fsb.loop_start; - vgmstream->loop_end_sample = fsb.loop_end; - vgmstream->num_streams = fsb.total_subsongs; - vgmstream->stream_size = fsb.stream_size; - vgmstream->meta_type = fsb.meta_type; - if (fsb.name_offset) - read_string(vgmstream->stream_name,fsb.name_size+1, fsb.name_offset,streamFile); - - switch(fsb.codec) { -#ifdef VGM_USE_MPEG - case MPEG: { /* FSB4: Shatter (PS3), Way of the Samurai 3/4 (PS3) */ - mpeg_custom_config cfg = {0}; - - cfg.fsb_padding = (vgmstream->channels > 2 ? 16 : - (fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED4 ? 4 : - (fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED ? 2 : 0))); - - vgmstream->codec_data = init_mpeg_custom(streamFile, fsb.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_FSB, &cfg); - if (!vgmstream->codec_data) goto fail; - vgmstream->layout_type = layout_none; - - //VGM_ASSERT(fsb.mode & FSOUND_MPEG_LAYER2, "FSB FSOUND_MPEG_LAYER2 found\n");/* not always set anyway */ - VGM_ASSERT(fsb.mode & FSOUND_IGNORETAGS, "FSB FSOUND_IGNORETAGS found\n"); /* not seen */ - break; - } -#endif - - case IMA: /* FSB3: Bioshock (PC), FSB4: Blade Kitten (PC) */ - vgmstream->coding_type = coding_XBOX_IMA; - vgmstream->layout_type = layout_none; - /* "interleaved header" IMA, only used with >2ch (ex. Blade Kitten 6ch) - * or (seemingly) when flag is used (ex. Dead to Rights 2 (Xbox) 2ch in FSB3.1) */ - if (vgmstream->channels > 2 || (fsb.mode & FSOUND_MULTICHANNEL)) - vgmstream->coding_type = coding_FSB_IMA; - - /* FSOUND_IMAADPCMSTEREO is "noninterleaved, true stereo IMA", but doesn't seem to be any different - * (found in FSB4: Shatter, Blade Kitten (PC), Hard Corps: Uprising (PS3)) */ - break; - - case PSX: /* FSB1: Jurassic Park Operation Genesis (PS2), FSB4: Spider Man Web of Shadows (PSP) */ - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_interleave; - if (fsb.flags & FMOD_FSB_SOURCE_NOTINTERLEAVED) { - vgmstream->interleave_block_size = fsb.stream_size / fsb.channels; - } - else { - vgmstream->interleave_block_size = 0x10; - } - break; - -#ifdef VGM_USE_FFMPEG - case XMA2: { /* FSB3: The Bourne Conspiracy 2008 (X360), FSB4: Armored Core V (X360), Hard Corps (X360) */ - uint8_t buf[0x100]; - size_t bytes, block_size, block_count; - - block_size = 0x8000; /* FSB default */ - block_count = fsb.stream_size / block_size; /* not accurate but not needed (custom_data_offset+0x14 -1?) */ - - bytes = ffmpeg_make_riff_xma2(buf,0x100, fsb.num_samples, fsb.stream_size, fsb.channels, fsb.sample_rate, block_count, block_size); - vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, fsb.stream_offset,fsb.stream_size); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_FFmpeg; - vgmstream->layout_type = layout_none; - - xma_fix_raw_samples(vgmstream, streamFile, fsb.stream_offset,fsb.stream_size, 0, 0,0); /* samples look ok */ - break; - } -#endif - - case DSP: /* FSB3: Metroid Prime 3 (GC), FSB4: de Blob (Wii) */ - if (fsb.flags & FMOD_FSB_SOURCE_NOTINTERLEAVED) { /* [de Blob (Wii) sfx)] */ - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = fsb.stream_size / fsb.channels; - } - else { - vgmstream->coding_type = coding_NGC_DSP_subint; - vgmstream->layout_type = layout_none; - vgmstream->interleave_block_size = 0x2; - } - dsp_read_coefs_be(vgmstream, streamFile, fsb.extradata_offset, 0x2e); - break; - -#ifdef VGM_USE_CELT - case CELT: { /* FSB4: War Thunder (PC), The Witcher 2 (PC), Vessel (PC) */ - int is_new_lib; - - /* get libcelt version (set in the first subsong only, but try all extradata just in case) */ - if (fsb.first_extradata_offset || fsb.extradata_offset) { - uint32_t lib = fsb.first_extradata_offset ? - (uint32_t)read_32bitLE(fsb.first_extradata_offset, streamFile) : - (uint32_t)read_32bitLE(fsb.extradata_offset, streamFile);; - switch(lib) { - case 0x80000009: is_new_lib = 0; break; /* War Thunder (PC) */ - case 0x80000010: is_new_lib = 1; break; /* Vessel (PC) */ - default: VGM_LOG("FSB: unknown CELT lib 0x%x\n", lib); goto fail; - } - } - else { - /* split FSBs? try to guess from observed bitstreams */ - uint16_t frame = (uint16_t)read_16bitBE(fsb.stream_offset+0x04+0x04,streamFile); - if ((frame & 0xF000) == 0x6000 || frame == 0xFFFE) { - is_new_lib = 1; - } else { - is_new_lib = 0; - } - } - - if (fsb.channels > 2) { /* multistreams */ - vgmstream->layout_data = build_layered_fsb_celt(streamFile, &fsb, is_new_lib); - if (!vgmstream->layout_data) goto fail; - vgmstream->coding_type = coding_CELT_FSB; - vgmstream->layout_type = layout_layered; - } - else { - vgmstream->codec_data = init_celt_fsb(vgmstream->channels, is_new_lib ? CELT_0_11_0 : CELT_0_06_1); - if (!vgmstream->codec_data) goto fail; - vgmstream->coding_type = coding_CELT_FSB; - vgmstream->layout_type = layout_none; - } - - break; - } -#endif - - case PCM8: /* assumed, no games known */ - vgmstream->coding_type = (fsb.mode & FSOUND_UNSIGNED) ? coding_PCM8_U : coding_PCM8; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x1; - break; - - case PCM16: /* (PCM16) FSB4: Rocket Knight (PC), Another Century's Episode R (PS3), Toy Story 3 (Wii) */ - vgmstream->coding_type = (fsb.flags & FMOD_FSB_SOURCE_BIGENDIANPCM) ? coding_PCM16BE : coding_PCM16LE; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x2; - - /* sometimes FSOUND_MONO/FSOUND_STEREO is not set (ex. Dead Space iOS), - * or only STEREO/MONO but not FSOUND_8BITS/FSOUND_16BITS is set */ - break; - - default: - goto fail; - } - - - if ( !vgmstream_open_stream(vgmstream, streamFile, fsb.stream_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - -#ifdef VGM_USE_CELT -static layered_layout_data* build_layered_fsb_celt(STREAMFILE *streamFile, fsb_header* fsb, int is_new_lib) { - layered_layout_data* data = NULL; - STREAMFILE* temp_streamFile = NULL; - int i, layers = (fsb->channels+1) / 2; - - - /* init layout */ - data = init_layout_layered(layers); - if (!data) goto fail; - - /* open each layer subfile (1/2ch CELT streams: 2ch+2ch..+1ch or 2ch+2ch..+2ch) */ - for (i = 0; i < layers; i++) { - int layer_channels = (i+1 == layers && fsb->channels % 2 == 1) - ? 1 : 2; /* last layer can be 1/2ch */ - - /* build the layer VGMSTREAM */ - data->layers[i] = allocate_vgmstream(layer_channels, fsb->loop_flag); - if (!data->layers[i]) goto fail; - - data->layers[i]->sample_rate = fsb->sample_rate; - data->layers[i]->num_samples = fsb->num_samples; - data->layers[i]->loop_start_sample = fsb->loop_start; - data->layers[i]->loop_end_sample = fsb->loop_end; - -#ifdef VGM_USE_CELT - data->layers[i]->codec_data = init_celt_fsb(layer_channels, is_new_lib ? CELT_0_11_0 : CELT_0_06_1); - if (!data->layers[i]->codec_data) goto fail; - data->layers[i]->coding_type = coding_CELT_FSB; - data->layers[i]->layout_type = layout_none; -#else - goto fail; -#endif - - temp_streamFile = setup_fsb_interleave_streamfile(streamFile, fsb->stream_offset, fsb->stream_size, layers, i, FSB_INT_CELT); - if (!temp_streamFile) goto fail; - - if ( !vgmstream_open_stream(data->layers[i], temp_streamFile, 0x00) ) { - goto fail; - } - } - - /* setup layered VGMSTREAMs */ - if (!setup_layout_layered(data)) - goto fail; - close_streamfile(temp_streamFile); - return data; - -fail: - close_streamfile(temp_streamFile); - free_layout_layered(data); - return NULL; -} -#endif - -/* ****************************************** */ - -static STREAMFILE* setup_fsb4_wav_streamfile(STREAMFILE *streamfile, off_t subfile_offset, size_t subfile_size); - -/* FSB4 with "\0WAV" Header, found in Deadly Creatures (Wii). - * Has a 0x10 BE header that holds the filesize (unsure if this is from a proper rip). */ -VGMSTREAM * init_vgmstream_fsb4_wav(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE *test_streamFile = NULL; - off_t subfile_start = 0x10; - size_t subfile_size = get_streamfile_size(streamFile) - 0x10 - 0x10; - - /* check extensions */ - if ( !check_extensions(streamFile, "fsb,wii") ) - goto fail; - - if (read_32bitBE(0x00,streamFile) != 0x00574156) /* "\0WAV" */ - goto fail; - - /* parse FSB subfile */ - test_streamFile = setup_fsb4_wav_streamfile(streamFile, subfile_start,subfile_size); - if (!test_streamFile) goto fail; - - vgmstream = init_vgmstream_fsb(test_streamFile); - if (!vgmstream) goto fail; - - /* init the VGMSTREAM */ - close_streamfile(test_streamFile); - return vgmstream; - -fail: - close_streamfile(test_streamFile); - close_vgmstream(vgmstream); - return NULL; -} - -static STREAMFILE* setup_fsb4_wav_streamfile(STREAMFILE *streamFile, off_t subfile_offset, size_t subfile_size) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - - /* setup subfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_clamp_streamfile(temp_streamFile, subfile_offset,subfile_size); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,"fsb"); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - return temp_streamFile; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" +#include "../layout/layout.h" +#include "fsb_interleave_streamfile.h" + + +/* ************************************************************************************************ + * FSB defines, copied from the public spec (https://www.fmod.org/questions/question/forum-4928/) + * for reference. The format is mostly compatible for FSB1/2/3/4, but not FSB5. + * ************************************************************************************************ */ +/* These flags are used for FMOD_FSB_HEADER::mode */ +#define FMOD_FSB_SOURCE_FORMAT 0x00000001 /* all samples stored in their original compressed format */ +#define FMOD_FSB_SOURCE_BASICHEADERS 0x00000002 /* samples should use the basic header structure */ +#define FMOD_FSB_SOURCE_ENCRYPTED 0x00000004 /* all sample data is encrypted */ +#define FMOD_FSB_SOURCE_BIGENDIANPCM 0x00000008 /* pcm samples have been written out in big-endian format */ +#define FMOD_FSB_SOURCE_NOTINTERLEAVED 0x00000010 /* Sample data is not interleaved. */ +#define FMOD_FSB_SOURCE_MPEG_PADDED 0x00000020 /* Mpeg frames are now rounded up to the nearest 2 bytes for normal sounds, or 16 bytes for multichannel. */ +#define FMOD_FSB_SOURCE_MPEG_PADDED4 0x00000040 /* Mpeg frames are now rounded up to the nearest 4 bytes for normal sounds, or 16 bytes for multichannel. */ + +/* These flags are used for FMOD_FSB_HEADER::version */ +#define FMOD_FSB_VERSION_3_0 0x00030000 /* FSB version 3.0 */ +#define FMOD_FSB_VERSION_3_1 0x00030001 /* FSB version 3.1 */ +#define FMOD_FSB_VERSION_4_0 0x00040000 /* FSB version 4.0 */ + +/* FMOD 3 defines. These flags are used for FMOD_FSB_SAMPLE_HEADER::mode */ +#define FSOUND_LOOP_OFF 0x00000001 /* For non looping samples. */ +#define FSOUND_LOOP_NORMAL 0x00000002 /* For forward looping samples. */ +#define FSOUND_LOOP_BIDI 0x00000004 /* For bidirectional looping samples. (no effect if in hardware). */ +#define FSOUND_8BITS 0x00000008 /* For 8 bit samples. */ +#define FSOUND_16BITS 0x00000010 /* For 16 bit samples. */ +#define FSOUND_MONO 0x00000020 /* For mono samples. */ +#define FSOUND_STEREO 0x00000040 /* For stereo samples. */ +#define FSOUND_UNSIGNED 0x00000080 /* For user created source data containing unsigned samples. */ +#define FSOUND_SIGNED 0x00000100 /* For user created source data containing signed data. */ +#define FSOUND_MPEG 0x00000200 /* For MPEG layer 2/3 data. */ +#define FSOUND_CHANNELMODE_ALLMONO 0x00000400 /* Sample is a collection of mono channels. */ +#define FSOUND_CHANNELMODE_ALLSTEREO 0x00000800 /* Sample is a collection of stereo channel pairs */ +#define FSOUND_HW3D 0x00001000 /* Attempts to make samples use 3d hardware acceleration. (if the card supports it) */ +#define FSOUND_2D 0x00002000 /* Tells software (not hardware) based sample not to be included in 3d processing. */ +#define FSOUND_SYNCPOINTS_NONAMES 0x00004000 /* Specifies that syncpoints are present with no names */ +#define FSOUND_DUPLICATE 0x00008000 /* This subsound is a duplicate of the previous one i.e. it uses the same sample data but w/different mode bits */ +#define FSOUND_CHANNELMODE_PROTOOLS 0x00010000 /* Sample is 6ch and uses L C R LS RS LFE standard. */ +#define FSOUND_MPEGACCURATE 0x00020000 /* For FSOUND_Stream_Open - for accurate FSOUND_Stream_GetLengthMs/FSOUND_Stream_SetTime. WARNING, see FSOUND_Stream_Open for inital opening time performance issues. */ +#define FSOUND_HW2D 0x00080000 /* 2D hardware sounds. allows hardware specific effects */ +#define FSOUND_3D 0x00100000 /* 3D software sounds */ +#define FSOUND_32BITS 0x00200000 /* For 32 bit (float) samples. */ +#define FSOUND_IMAADPCM 0x00400000 /* Contents are stored compressed as IMA ADPCM */ +#define FSOUND_VAG 0x00800000 /* For PS2 only - Contents are compressed as Sony VAG format */ +#define FSOUND_XMA 0x01000000 /* For Xbox360 only - Contents are compressed as XMA format */ +#define FSOUND_GCADPCM 0x02000000 /* For Gamecube only - Contents are compressed as Gamecube DSP-ADPCM format */ +#define FSOUND_MULTICHANNEL 0x04000000 /* For PS2 and Gamecube only - Contents are interleaved into a multi-channel (more than stereo) format */ +#define FSOUND_OGG 0x08000000 /* For vorbis encoded ogg data */ +#define FSOUND_CELT 0x08000000 /* For vorbis encoded ogg data */ +#define FSOUND_MPEG_LAYER3 0x10000000 /* Data is in MP3 format. */ +#define FSOUND_MPEG_LAYER2 0x00040000 /* Data is in MP2 format. */ +#define FSOUND_LOADMEMORYIOP 0x20000000 /* For PS2 only - "name" will be interpreted as a pointer to data for streaming and samples. The address provided will be an IOP address */ +#define FSOUND_IMAADPCMSTEREO 0x20000000 /* Signify IMA ADPCM is actually stereo not two interleaved mono */ +#define FSOUND_IGNORETAGS 0x40000000 /* Skips id3v2 etc tag checks when opening a stream, to reduce seek/read overhead when opening files (helps with CD performance) */ +#define FSOUND_SYNCPOINTS 0x80000000 /* Specifies that syncpoints are present */ + +/* These flags are used for FMOD_FSB_SAMPLE_HEADER::mode */ +#define FSOUND_CHANNELMODE_MASK (FSOUND_CHANNELMODE_ALLMONO | FSOUND_CHANNELMODE_ALLSTEREO | FSOUND_CHANNELMODE_PROTOOLS) +#define FSOUND_CHANNELMODE_DEFAULT 0x00000000 /* Determine channel assignment automatically from channel count. */ +#define FSOUND_CHANNELMODE_RESERVED 0x00000C00 +#define FSOUND_NORMAL (FSOUND_16BITS | FSOUND_SIGNED | FSOUND_MONO) +#define FSB_SAMPLE_DATA_ALIGN 32 + + +/* simplified struct based on the original definitions */ +typedef enum { MPEG, IMA, PSX, XMA, DSP, CELT, PCM8, PCM16 } fsb_codec_t; +typedef struct { + /* main header */ + uint32_t id; + int32_t total_subsongs; + uint32_t sample_headers_size; /* all of them including extended information */ + uint32_t sample_data_size; + uint32_t version; /* extended fsb version (in FSB 3/3.1/4) */ + uint32_t flags; /* flags common to all streams (in FSB 3/3.1/4)*/ + /* sample header */ + uint32_t num_samples; + uint32_t stream_size; + uint32_t loop_start; + uint32_t loop_end; + uint32_t mode; + int32_t sample_rate; + uint16_t channels; + /* extra */ + uint32_t base_header_size; + uint32_t sample_header_min; + off_t extradata_offset; + off_t first_extradata_offset; + + meta_t meta_type; + off_t name_offset; + size_t name_size; + + int loop_flag; + + off_t stream_offset; + + fsb_codec_t codec; +} fsb_header; + +/* ********************************************************************************** */ + +static layered_layout_data* build_layered_fsb_celt(STREAMFILE* sf, fsb_header* fsb, int is_new_lib); + +/* FSB1~4 - from games using FMOD audio middleware */ +VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + int target_subsong = sf->stream_index; + fsb_header fsb = {0}; + + + /* checks + * .fsb: standard + * .bnk: Hard Corps Uprising (PS3) */ + if ( !check_extensions(sf, "fsb,bnk") ) + goto fail; + + /* check header */ + fsb.id = read_32bitBE(0x00,sf); + if (fsb.id == 0x46534231) { /* "FSB1" (somewhat different from other fsbs) */ + fsb.meta_type = meta_FSB1; + fsb.base_header_size = 0x10; + fsb.sample_header_min = 0x40; + + /* main header */ + fsb.total_subsongs = read_32bitLE(0x04,sf); + fsb.sample_data_size = read_32bitLE(0x08,sf); + fsb.sample_headers_size = 0x40; + fsb.version = 0; + fsb.flags = 0; + + if (fsb.total_subsongs > 1) goto fail; + + /* sample header (first stream only, not sure if there are multi-FSB1) */ + { + off_t header_offset = fsb.base_header_size; + + fsb.name_offset = header_offset; + fsb.name_size = 0x20; + fsb.num_samples = read_32bitLE(header_offset+0x20,sf); + fsb.stream_size = read_32bitLE(header_offset+0x24,sf); + fsb.sample_rate = read_32bitLE(header_offset+0x28,sf); + /* 0x2c:? 0x2e:? 0x30:? 0x32:? */ + fsb.mode = read_32bitLE(header_offset+0x34,sf); + fsb.loop_start = read_32bitLE(header_offset+0x38,sf); + fsb.loop_end = read_32bitLE(header_offset+0x3c,sf); + + VGM_ASSERT(fsb.loop_end > fsb.num_samples, "FSB: loop end over samples (%i vs %i)\n", fsb.loop_end, fsb.num_samples); + fsb.channels = (fsb.mode & FSOUND_STEREO) ? 2 : 1; + if (fsb.loop_end > fsb.num_samples) /* this seems common... */ + fsb.num_samples = fsb.loop_end; + + /* DSP coefs, seek tables, etc */ + fsb.extradata_offset = header_offset+fsb.sample_header_min; + + fsb.stream_offset = fsb.base_header_size + fsb.sample_headers_size; + } + } + else { /* other FSBs (common/extended format) */ + if (fsb.id == 0x46534232) { /* "FSB2" */ + fsb.meta_type = meta_FSB2; + fsb.base_header_size = 0x10; + fsb.sample_header_min = 0x40; /* guessed */ + } else if (fsb.id == 0x46534233) { /* "FSB3" */ + fsb.meta_type = meta_FSB3; + fsb.base_header_size = 0x18; + fsb.sample_header_min = 0x40; + } else if (fsb.id == 0x46534234) { /* "FSB4" */ + fsb.meta_type = meta_FSB4; + fsb.base_header_size = 0x30; + fsb.sample_header_min = 0x50; + } else { + goto fail; + } + + /* main header */ + fsb.total_subsongs = read_32bitLE(0x04,sf); + fsb.sample_headers_size = read_32bitLE(0x08,sf); + fsb.sample_data_size = read_32bitLE(0x0c,sf); + if (fsb.base_header_size > 0x10) { + fsb.version = read_32bitLE(0x10,sf); + fsb.flags = read_32bitLE(0x14,sf); + /* FSB4: 0x18(8):hash 0x20(10):guid */ + } else { + fsb.version = 0; + fsb.flags = 0; + } + + if (fsb.version == FMOD_FSB_VERSION_3_1) { + fsb.sample_header_min = 0x50; + } else if (fsb.version != 0 /* FSB2 */ + && fsb.version != FMOD_FSB_VERSION_3_0 + && fsb.version != FMOD_FSB_VERSION_4_0) { + goto fail; + } + + if (fsb.sample_headers_size < fsb.sample_header_min) goto fail; + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > fsb.total_subsongs || fsb.total_subsongs < 1) goto fail; + + /* sample header (N-stream) */ + { + int i; + off_t header_offset = fsb.base_header_size; + off_t data_offset = fsb.base_header_size + fsb.sample_headers_size; + + /* find target_stream header (variable sized) */ + for (i = 0; i < fsb.total_subsongs; i++) { + size_t stream_header_size; + + if ((fsb.flags & FMOD_FSB_SOURCE_BASICHEADERS) && i > 0) { + /* miniheader, all subsongs reuse first header [rare, ex. Biker Mice from Mars (PS2)] */ + stream_header_size = 0x08; + fsb.num_samples = read_32bitLE(header_offset+0x00,sf); + fsb.stream_size = read_32bitLE(header_offset+0x04,sf); + fsb.loop_start = 0; + fsb.loop_end = 0; + } + else { + /* subsong header for normal files */ + stream_header_size = (uint16_t)read_16bitLE(header_offset+0x00,sf); + fsb.name_offset = header_offset+0x02; + fsb.name_size = 0x20-0x02; + fsb.num_samples = read_32bitLE(header_offset+0x20,sf); + fsb.stream_size = read_32bitLE(header_offset+0x24,sf); + fsb.loop_start = read_32bitLE(header_offset+0x28,sf); + fsb.loop_end = read_32bitLE(header_offset+0x2c,sf); + fsb.mode = read_32bitLE(header_offset+0x30,sf); + fsb.sample_rate = read_32bitLE(header_offset+0x34,sf); + /* 0x38: defvol, 0x3a: defpan, 0x3c: defpri */ + fsb.channels = read_16bitLE(header_offset+0x3e,sf); + /* FSB3.1/4: + * 0x40: mindistance, 0x44: maxdistance, 0x48: varfreq/size_32bits + * 0x4c: varvol, 0x4e: fsb.varpan */ + + /* DSP coefs, seek tables, etc */ + if (stream_header_size > fsb.sample_header_min) { + fsb.extradata_offset = header_offset+fsb.sample_header_min; + if (fsb.first_extradata_offset == 0) + fsb.first_extradata_offset = fsb.extradata_offset; + } + } + + if (i+1 == target_subsong) /* final data_offset found */ + break; + + header_offset += stream_header_size; + data_offset += fsb.stream_size; /* there is no offset so manually count */ + + /* some subsongs offsets need padding (most FSOUND_IMAADPCM, few MPEG too [Hard Reset (PC) subsong 5]) + * other codecs may set PADDED4 (ex. XMA) but don't seem to need it and work fine */ + if (fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED4) { + if (data_offset % 0x20) + data_offset += 0x20 - (data_offset % 0x20); + } + } + if (i > fsb.total_subsongs) + goto fail; /* not found */ + + fsb.stream_offset = data_offset; + } + } + + + /* convert to clean some code */ + if (fsb.mode & FSOUND_MPEG) fsb.codec = MPEG; + else if (fsb.mode & FSOUND_IMAADPCM) fsb.codec = IMA; + else if (fsb.mode & FSOUND_VAG) fsb.codec = PSX; + else if (fsb.mode & FSOUND_XMA) fsb.codec = XMA; + else if (fsb.mode & FSOUND_GCADPCM) fsb.codec = DSP; + else if (fsb.mode & FSOUND_CELT) fsb.codec = CELT; + else if (fsb.mode & FSOUND_8BITS) fsb.codec = PCM8; + else fsb.codec = PCM16; + + /* correct compared to FMOD's tools */ + if (fsb.loop_end) + fsb.loop_end += 1; + + /* ping-pong looping = no looping? (forward > reverse > forward) [ex. Biker Mice from Mars (PS2)] */ + VGM_ASSERT(fsb.mode & FSOUND_LOOP_BIDI, "FSB BIDI looping found\n"); + VGM_ASSERT(fsb.mode & FSOUND_LOOP_OFF, "FSB LOOP OFF found\n"); /* sometimes used */ + VGM_ASSERT(fsb.mode & FSOUND_LOOP_NORMAL, "FSB LOOP NORMAL found\n"); /* very rarely set */ + /* XOR encryption for some FSB4, though the flag is only seen after decrypting */ + //;VGM_ASSERT(fsb.flags & FMOD_FSB_SOURCE_ENCRYPTED, "FSB ENCRYPTED found\n"); + + /* sometimes there is garbage at the end or missing bytes due to improper ripping */ + VGM_ASSERT(fsb.base_header_size + fsb.sample_headers_size + fsb.sample_data_size != sf->get_size(sf), + "FSB wrong head/data_size found (expected 0x%x vs 0x%x)\n", + fsb.base_header_size + fsb.sample_headers_size + fsb.sample_data_size, sf->get_size(sf)); + + /* autodetect unwanted loops */ + { + /* FMOD tool's default behaviour is creating files with full loops and no flags unless disabled + * manually (can be overriden during program too), for all FSB versions. This makes jingles/sfx/voices + * loop when they shouldn't, but most music does full loops seamlessly, so we only want to disable + * if it looks jingly enough. Incidentally, their tools can only make files with full loops. */ + int enable_loop, full_loop, is_small; + + /* seems to mean forced loop */ + enable_loop = (fsb.mode & FSOUND_LOOP_NORMAL); + + /* for MPEG and CELT sometimes full loops are given with around/exact 1 frame less than num_samples, + * probably to account for encoder/decoder delay (ex. The Witcher 2, Hard Reset, Timeshift) */ + if (fsb.codec == CELT) + full_loop = fsb.loop_start - 512 <= 0 && fsb.loop_end >= fsb.num_samples - 512; /* aproximate */ + else if (fsb.codec == MPEG) + full_loop = fsb.loop_start - 1152 <= 0 && fsb.loop_end >= fsb.num_samples - 1152; /* WWF Legends of Wrestlemania uses 2 frames? */ + else + full_loop = fsb.loop_start == 0 && fsb.loop_end == fsb.num_samples; + + /* in seconds (lame but no better way) */ + is_small = fsb.num_samples < 20 * fsb.sample_rate; + + //;VGM_LOG("FSB: loop start=%i, loop end=%i, samples=%i, mode=%x\n", fsb.loop_start, fsb.loop_end, fsb.num_samples, fsb.mode); + //;VGM_LOG("FSB: enable=%i, full=%i, small=%i\n",enable_loop,full_loop,is_small ); + + fsb.loop_flag = !(fsb.mode & FSOUND_LOOP_OFF); /* disabled manually */ + if (fsb.loop_flag && !enable_loop && full_loop && is_small) { + VGM_LOG("FSB: disable unwanted loop\n"); + fsb.loop_flag = 0; + } + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(fsb.channels,fsb.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = fsb.sample_rate; + vgmstream->num_samples = fsb.num_samples; + vgmstream->loop_start_sample = fsb.loop_start; + vgmstream->loop_end_sample = fsb.loop_end; + vgmstream->num_streams = fsb.total_subsongs; + vgmstream->stream_size = fsb.stream_size; + vgmstream->meta_type = fsb.meta_type; + if (fsb.name_offset) + read_string(vgmstream->stream_name,fsb.name_size+1, fsb.name_offset,sf); + + switch(fsb.codec) { +#ifdef VGM_USE_MPEG + case MPEG: { /* FSB4: Shatter (PS3), Way of the Samurai 3/4 (PS3) */ + mpeg_custom_config cfg = {0}; + + cfg.fsb_padding = (vgmstream->channels > 2 ? 16 : + (fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED4 ? 4 : + (fsb.flags & FMOD_FSB_SOURCE_MPEG_PADDED ? 2 : 0))); + + vgmstream->codec_data = init_mpeg_custom(sf, fsb.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_FSB, &cfg); + if (!vgmstream->codec_data) goto fail; + vgmstream->layout_type = layout_none; + + //VGM_ASSERT(fsb.mode & FSOUND_MPEG_LAYER2, "FSB FSOUND_MPEG_LAYER2 found\n");/* not always set anyway */ + VGM_ASSERT(fsb.mode & FSOUND_IGNORETAGS, "FSB FSOUND_IGNORETAGS found\n"); /* not seen */ + break; + } +#endif + + case IMA: /* FSB3: Bioshock (PC), FSB4: Blade Kitten (PC) */ + vgmstream->coding_type = coding_XBOX_IMA; + vgmstream->layout_type = layout_none; + /* "interleaved header" IMA, only used with >2ch (ex. Blade Kitten 6ch) + * or (seemingly) when flag is used (ex. Dead to Rights 2 (Xbox) 2ch in FSB3.1) */ + if (vgmstream->channels > 2 || (fsb.mode & FSOUND_MULTICHANNEL)) + vgmstream->coding_type = coding_FSB_IMA; + + /* FSOUND_IMAADPCMSTEREO is "noninterleaved, true stereo IMA", but doesn't seem to be any different + * (found in FSB4: Shatter, Blade Kitten (PC), Hard Corps: Uprising (PS3)) */ + break; + + case PSX: /* FSB1: Jurassic Park Operation Genesis (PS2), FSB4: Spider Man Web of Shadows (PSP) */ + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + if (fsb.flags & FMOD_FSB_SOURCE_NOTINTERLEAVED) { + vgmstream->interleave_block_size = fsb.stream_size / fsb.channels; + } + else { + vgmstream->interleave_block_size = 0x10; + } + break; + +#ifdef VGM_USE_FFMPEG + case XMA: { /* FSB3: The Bourne Conspiracy 2008 (X360), FSB4: Armored Core V (X360), Hard Corps (X360) */ + uint8_t buf[0x100]; + size_t bytes, block_size, block_count; + + if (fsb.version != FMOD_FSB_VERSION_4_0) { /* 3.x, though no actual output changes [ex. Guitar Hero III (X360)] */ + bytes = ffmpeg_make_riff_xma1(buf, sizeof(buf), fsb.num_samples, fsb.stream_size, fsb.channels, fsb.sample_rate, 0); + } + else { + block_size = 0x8000; /* FSB default */ + block_count = fsb.stream_size / block_size; /* not accurate but not needed (custom_data_offset+0x14 -1?) */ + + bytes = ffmpeg_make_riff_xma2(buf, sizeof(buf), fsb.num_samples, fsb.stream_size, fsb.channels, fsb.sample_rate, block_count, block_size); + } + vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, fsb.stream_offset,fsb.stream_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + xma_fix_raw_samples(vgmstream, sf, fsb.stream_offset,fsb.stream_size, 0, 0,0); /* samples look ok */ + break; + } +#endif + + case DSP: /* FSB3: Metroid Prime 3 (GC), FSB4: de Blob (Wii) */ + if (fsb.flags & FMOD_FSB_SOURCE_NOTINTERLEAVED) { /* [de Blob (Wii) sfx)] */ + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = fsb.stream_size / fsb.channels; + } + else { + vgmstream->coding_type = coding_NGC_DSP_subint; + vgmstream->layout_type = layout_none; + vgmstream->interleave_block_size = 0x2; + } + dsp_read_coefs_be(vgmstream, sf, fsb.extradata_offset, 0x2e); + break; + +#ifdef VGM_USE_CELT + case CELT: { /* FSB4: War Thunder (PC), The Witcher 2 (PC), Vessel (PC) */ + int is_new_lib; + + /* get libcelt version (set in the first subsong only, but try all extradata just in case) */ + if (fsb.first_extradata_offset || fsb.extradata_offset) { + uint32_t lib = fsb.first_extradata_offset ? + (uint32_t)read_32bitLE(fsb.first_extradata_offset, sf) : + (uint32_t)read_32bitLE(fsb.extradata_offset, sf);; + switch(lib) { + case 0x80000009: is_new_lib = 0; break; /* War Thunder (PC) */ + case 0x80000010: is_new_lib = 1; break; /* Vessel (PC) */ + default: VGM_LOG("FSB: unknown CELT lib 0x%x\n", lib); goto fail; + } + } + else { + /* split FSBs? try to guess from observed bitstreams */ + uint16_t frame = (uint16_t)read_16bitBE(fsb.stream_offset+0x04+0x04,sf); + if ((frame & 0xF000) == 0x6000 || frame == 0xFFFE) { + is_new_lib = 1; + } else { + is_new_lib = 0; + } + } + + if (fsb.channels > 2) { /* multistreams */ + vgmstream->layout_data = build_layered_fsb_celt(sf, &fsb, is_new_lib); + if (!vgmstream->layout_data) goto fail; + vgmstream->coding_type = coding_CELT_FSB; + vgmstream->layout_type = layout_layered; + } + else { + vgmstream->codec_data = init_celt_fsb(vgmstream->channels, is_new_lib ? CELT_0_11_0 : CELT_0_06_1); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_CELT_FSB; + vgmstream->layout_type = layout_none; + } + + break; + } +#endif + + case PCM8: /* assumed, no games known */ + vgmstream->coding_type = (fsb.mode & FSOUND_UNSIGNED) ? coding_PCM8_U : coding_PCM8; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x1; + break; + + case PCM16: /* (PCM16) FSB4: Rocket Knight (PC), Another Century's Episode R (PS3), Toy Story 3 (Wii) */ + vgmstream->coding_type = (fsb.flags & FMOD_FSB_SOURCE_BIGENDIANPCM) ? coding_PCM16BE : coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x2; + + /* sometimes FSOUND_MONO/FSOUND_STEREO is not set (ex. Dead Space iOS), + * or only STEREO/MONO but not FSOUND_8BITS/FSOUND_16BITS is set */ + break; + + default: + goto fail; + } + + + if ( !vgmstream_open_stream(vgmstream, sf, fsb.stream_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +#ifdef VGM_USE_CELT +static layered_layout_data* build_layered_fsb_celt(STREAMFILE* sf, fsb_header* fsb, int is_new_lib) { + layered_layout_data* data = NULL; + STREAMFILE* temp_sf = NULL; + int i, layers = (fsb->channels+1) / 2; + + + /* init layout */ + data = init_layout_layered(layers); + if (!data) goto fail; + + /* open each layer subfile (1/2ch CELT streams: 2ch+2ch..+1ch or 2ch+2ch..+2ch) */ + for (i = 0; i < layers; i++) { + int layer_channels = (i+1 == layers && fsb->channels % 2 == 1) + ? 1 : 2; /* last layer can be 1/2ch */ + + /* build the layer VGMSTREAM */ + data->layers[i] = allocate_vgmstream(layer_channels, fsb->loop_flag); + if (!data->layers[i]) goto fail; + + data->layers[i]->sample_rate = fsb->sample_rate; + data->layers[i]->num_samples = fsb->num_samples; + data->layers[i]->loop_start_sample = fsb->loop_start; + data->layers[i]->loop_end_sample = fsb->loop_end; + +#ifdef VGM_USE_CELT + data->layers[i]->codec_data = init_celt_fsb(layer_channels, is_new_lib ? CELT_0_11_0 : CELT_0_06_1); + if (!data->layers[i]->codec_data) goto fail; + data->layers[i]->coding_type = coding_CELT_FSB; + data->layers[i]->layout_type = layout_none; +#else + goto fail; +#endif + + temp_sf = setup_fsb_interleave_streamfile(sf, fsb->stream_offset, fsb->stream_size, layers, i, FSB_INT_CELT); + if (!temp_sf) goto fail; + + if (!vgmstream_open_stream(data->layers[i], temp_sf, 0x00)) + goto fail; + + close_streamfile(temp_sf); + temp_sf = NULL; + } + + /* setup layered VGMSTREAMs */ + if (!setup_layout_layered(data)) + goto fail; + return data; + +fail: + close_streamfile(temp_sf); + free_layout_layered(data); + return NULL; +} +#endif + +/* ****************************************** */ + +static STREAMFILE* setup_fsb4_wav_streamfile(STREAMFILE *streamfile, off_t subfile_offset, size_t subfile_size); + +/* FSB4 with "\0WAV" Header, found in Deadly Creatures (Wii). + * Has a 0x10 BE header that holds the filesize (unsure if this is from a proper rip). */ +VGMSTREAM* init_vgmstream_fsb4_wav(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE *test_sf = NULL; + off_t subfile_start = 0x10; + size_t subfile_size = get_streamfile_size(sf) - 0x10 - 0x10; + + /* check extensions */ + if ( !check_extensions(sf, "fsb,wii") ) + goto fail; + + if (read_32bitBE(0x00,sf) != 0x00574156) /* "\0WAV" */ + goto fail; + + /* parse FSB subfile */ + test_sf = setup_fsb4_wav_streamfile(sf, subfile_start,subfile_size); + if (!test_sf) goto fail; + + vgmstream = init_vgmstream_fsb(test_sf); + if (!vgmstream) goto fail; + + /* init the VGMSTREAM */ + close_streamfile(test_sf); + return vgmstream; + +fail: + close_streamfile(test_sf); + close_vgmstream(vgmstream); + return NULL; +} + +static STREAMFILE* setup_fsb4_wav_streamfile(STREAMFILE* sf, off_t subfile_offset, size_t subfile_size) { + STREAMFILE *temp_sf = NULL, *new_sf = NULL; + + /* setup subfile */ + new_sf = open_wrap_streamfile(sf); + if (!new_sf) goto fail; + temp_sf = new_sf; + + new_sf = open_clamp_streamfile(temp_sf, subfile_offset,subfile_size); + if (!new_sf) goto fail; + temp_sf = new_sf; + + new_sf = open_fakename_streamfile(temp_sf, NULL,"fsb"); + if (!new_sf) goto fail; + temp_sf = new_sf; + + return temp_sf; + +fail: + close_streamfile(temp_sf); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/fsb5.c b/Frameworks/vgmstream/vgmstream/src/meta/fsb5.c index bd4a6c252..4f33a252a 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/fsb5.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/fsb5.c @@ -33,45 +33,45 @@ typedef struct { /* ********************************************************************************** */ -static layered_layout_data* build_layered_fsb5_celt(STREAMFILE *streamFile, fsb5_header* fsb5); -static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE *streamFile, fsb5_header* fsb5, off_t configs_offset, size_t configs_size); +static layered_layout_data* build_layered_fsb5_celt(STREAMFILE* sf, fsb5_header* fsb5); +static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE* sf, fsb5_header* fsb5, off_t configs_offset, size_t configs_size); /* FSB5 - FMOD Studio multiplatform format */ -VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; +VGMSTREAM* init_vgmstream_fsb5(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; fsb5_header fsb5 = {0}; - int target_subsong = streamFile->stream_index; + int target_subsong = sf->stream_index; int i; /* checks */ /* .fsb: standard * .snd: Alchemy engine (also Unity) */ - if (!check_extensions(streamFile,"fsb,snd")) + if (!check_extensions(sf,"fsb,snd")) goto fail; - if (read_32bitBE(0x00,streamFile) != 0x46534235) /* "FSB5" */ + if (read_32bitBE(0x00,sf) != 0x46534235) /* "FSB5" */ goto fail; /* 0x00 is rare (seen in Tales from Space Vita) */ - fsb5.version = read_32bitLE(0x04,streamFile); + fsb5.version = read_32bitLE(0x04,sf); if (fsb5.version != 0x00 && fsb5.version != 0x01) goto fail; - fsb5.total_subsongs = read_32bitLE(0x08,streamFile); - fsb5.sample_header_size = read_32bitLE(0x0C,streamFile); - fsb5.name_table_size = read_32bitLE(0x10,streamFile); - fsb5.sample_data_size = read_32bitLE(0x14,streamFile); - fsb5.codec = read_32bitLE(0x18,streamFile); + fsb5.total_subsongs = read_32bitLE(0x08,sf); + fsb5.sample_header_size = read_32bitLE(0x0C,sf); + fsb5.name_table_size = read_32bitLE(0x10,sf); + fsb5.sample_data_size = read_32bitLE(0x14,sf); + fsb5.codec = read_32bitLE(0x18,sf); /* version 0x01 - 0x1c(4): zero, 0x24(16): hash, 0x34(8): unk * version 0x00 has an extra field (always 0?) at 0x1c */ if (fsb5.version == 0x01) { /* found by tests and assumed to be flags, no games known */ - fsb5.flags = read_32bitLE(0x20,streamFile); + fsb5.flags = read_32bitLE(0x20,sf); } fsb5.base_header_size = (fsb5.version==0x00) ? 0x40 : 0x3C; - if ((fsb5.sample_header_size + fsb5.name_table_size + fsb5.sample_data_size + fsb5.base_header_size) != get_streamfile_size(streamFile)) { - VGM_LOG("FSB5: bad size (%x + %x + %x + %x != %x)\n", fsb5.sample_header_size, fsb5.name_table_size, fsb5.sample_data_size, fsb5.base_header_size, get_streamfile_size(streamFile)); + if ((fsb5.sample_header_size + fsb5.name_table_size + fsb5.sample_data_size + fsb5.base_header_size) != get_streamfile_size(sf)) { + VGM_LOG("FSB5: bad size (%x + %x + %x + %x != %x)\n", fsb5.sample_header_size, fsb5.name_table_size, fsb5.sample_data_size, fsb5.base_header_size, get_streamfile_size(sf)); goto fail; } @@ -87,8 +87,8 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { off_t data_offset = 0; uint32_t sample_mode1, sample_mode2; /* maybe one uint64? */ - sample_mode1 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+0x00,streamFile); - sample_mode2 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+0x04,streamFile); + sample_mode1 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+0x00,sf); + sample_mode2 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+0x04,sf); stream_header_size += 0x08; /* get samples */ @@ -133,7 +133,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { uint32_t extraflag, extraflag_type, extraflag_size, extraflag_end; do { - extraflag = read_32bitLE(extraflag_offset,streamFile); + extraflag = read_32bitLE(extraflag_offset,sf); extraflag_type = (extraflag >> 25) & 0x7F; /* bits 32..26 (7) */ extraflag_size = (extraflag >> 1) & 0xFFFFFF; /* bits 25..1 (24)*/ extraflag_end = (extraflag & 0x01); /* bit 0 (1) */ @@ -142,15 +142,15 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { if (i + 1 == target_subsong) { switch(extraflag_type) { case 0x01: /* channels */ - fsb5.channels = read_8bit(extraflag_offset+0x04,streamFile); + fsb5.channels = read_8bit(extraflag_offset+0x04,sf); break; case 0x02: /* sample rate */ - fsb5.sample_rate = read_32bitLE(extraflag_offset+0x04,streamFile); + fsb5.sample_rate = read_32bitLE(extraflag_offset+0x04,sf); break; case 0x03: /* loop info */ - fsb5.loop_start = read_32bitLE(extraflag_offset+0x04,streamFile); + fsb5.loop_start = read_32bitLE(extraflag_offset+0x04,sf); if (extraflag_size > 0x04) { /* probably not needed */ - fsb5.loop_end = read_32bitLE(extraflag_offset+0x08,streamFile); + fsb5.loop_end = read_32bitLE(extraflag_offset+0x08,sf); fsb5.loop_end += 1; /* correct compared to FMOD's tools */ } //;VGM_LOG("FSB5: stream %i loop start=%i, loop end=%i, samples=%i\n", i, fsb5.loop_start, fsb5.loop_end, fsb5.num_samples); @@ -183,7 +183,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { case 0x05: /* unknown 32b */ /* rare, found in Tearaway (Vita) with value 0 in first stream and * Shantae and the Seven Sirens (Mobile) with value 0x0003bd72 BE in #44 (Arena Town) */ - VGM_LOG("FSB5: stream %i flag %x with value %08x\n", i, extraflag_type, read_32bitLE(extraflag_offset+0x04,streamFile)); + VGM_LOG("FSB5: stream %i flag %x with value %08x\n", i, extraflag_type, read_32bitLE(extraflag_offset+0x04,sf)); break; case 0x06: /* XMA seek table */ /* no need for it */ @@ -209,7 +209,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { break; case 0x0d: /* unknown 32b (config? usually 0x3fnnnn00 BE and sometimes 0x3dnnnn00 BE) */ /* found in some XMA2/Vorbis/FADPCM */ - VGM_LOG("FSB5: stream %i flag %x with value %08x\n", i, extraflag_type, read_32bitLE(extraflag_offset+0x04,streamFile)); + VGM_LOG("FSB5: stream %i flag %x with value %08x\n", i, extraflag_type, read_32bitLE(extraflag_offset+0x04,sf)); break; default: VGM_LOG("FSB5: stream %i unknown flag 0x%x at %x + 0x04 (size 0x%x)\n", i, extraflag_type, (uint32_t)extraflag_offset, extraflag_size); @@ -233,8 +233,8 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { else { off_t next_data_offset; uint32_t next_sample_mode1, next_sample_mode2; - next_sample_mode1 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+stream_header_size+0x00,streamFile); - next_sample_mode2 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+stream_header_size+0x04,streamFile); + next_sample_mode1 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+stream_header_size+0x00,sf); + next_sample_mode2 = (uint32_t)read_32bitLE(fsb5.sample_header_offset+stream_header_size+0x04,sf); next_data_offset = (((next_sample_mode2 & 0x03) << 25) | ((next_sample_mode1 >> 7) & 0x1FFFFFF)) << 5; fsb5.stream_size = next_data_offset - data_offset; @@ -252,7 +252,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { /* get stream name */ if (fsb5.name_table_size) { off_t name_suboffset = fsb5.base_header_size + fsb5.sample_header_size + 0x04*(target_subsong-1); - fsb5.name_offset = fsb5.base_header_size + fsb5.sample_header_size + read_32bitLE(name_suboffset,streamFile); + fsb5.name_offset = fsb5.base_header_size + fsb5.sample_header_size + read_32bitLE(name_suboffset,sf); } @@ -270,7 +270,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { vgmstream->stream_size = fsb5.stream_size; vgmstream->meta_type = meta_FSB5; if (fsb5.name_offset) - read_string(vgmstream->stream_name,STREAM_NAME_SIZE, fsb5.name_offset,streamFile); + read_string(vgmstream->stream_name,STREAM_NAME_SIZE, fsb5.name_offset,sf); switch (fsb5.codec) { case 0x00: /* FMOD_SOUND_FORMAT_NONE */ @@ -313,7 +313,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { vgmstream->layout_type = layout_none; vgmstream->interleave_block_size = 0x02; } - dsp_read_coefs_be(vgmstream,streamFile,fsb5.extradata_offset,0x2E); + dsp_read_coefs_be(vgmstream,sf,fsb5.extradata_offset,0x2E); break; case 0x07: /* FMOD_SOUND_FORMAT_IMAADPCM [Skylanders] */ @@ -347,12 +347,12 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { block_count = fsb5.stream_size / block_size + (fsb5.stream_size % block_size ? 1 : 0); bytes = ffmpeg_make_riff_xma2(buf, 0x100, vgmstream->num_samples, fsb5.stream_size, vgmstream->channels, vgmstream->sample_rate, block_count, block_size); - vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, fsb5.stream_offset,fsb5.stream_size); + vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, fsb5.stream_offset,fsb5.stream_size); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - xma_fix_raw_samples(vgmstream, streamFile, fsb5.stream_offset,fsb5.stream_size, 0, 0,0); /* samples look ok */ + xma_fix_raw_samples(vgmstream, sf, fsb5.stream_offset,fsb5.stream_size, 0, 0,0); /* samples look ok */ break; } #endif @@ -363,7 +363,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { cfg.fsb_padding = (vgmstream->channels > 2 ? 16 : 4); /* observed default */ - vgmstream->codec_data = init_mpeg_custom(streamFile, fsb5.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_FSB, &cfg); + vgmstream->codec_data = init_mpeg_custom(sf, fsb5.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_FSB, &cfg); if (!vgmstream->codec_data) goto fail; vgmstream->layout_type = layout_none; break; @@ -375,7 +375,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { int is_multistream = fsb5.channels > 2; if (is_multistream) { - vgmstream->layout_data = build_layered_fsb5_celt(streamFile, &fsb5); + vgmstream->layout_data = build_layered_fsb5_celt(sf, &fsb5); if (!vgmstream->layout_data) goto fail; vgmstream->coding_type = coding_CELT_FSB; vgmstream->layout_type = layout_layered; @@ -398,7 +398,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { /* skip frame size in newer FSBs [Day of the Tentacle Remastered (Vita), Tearaway Unfolded (PS4)] */ - if (configs_size >= 0x08 && (uint8_t)read_8bit(configs_offset, streamFile) != 0xFE) { /* ATRAC9 sync */ + if (configs_size >= 0x08 && (uint8_t)read_8bit(configs_offset, sf) != 0xFE) { /* ATRAC9 sync */ configs_offset += 0x04; configs_size -= 0x04; } @@ -407,7 +407,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { if (is_multistream) { /* multichannel made of various streams [Little Big Planet (Vita)] */ - vgmstream->layout_data = build_layered_fsb5_atrac9(streamFile, &fsb5, configs_offset, configs_size); + vgmstream->layout_data = build_layered_fsb5_atrac9(sf, &fsb5, configs_offset, configs_size); if (!vgmstream->layout_data) goto fail; vgmstream->coding_type = coding_ATRAC9; vgmstream->layout_type = layout_layered; @@ -417,7 +417,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { atrac9_config cfg = {0}; cfg.channels = vgmstream->channels; - cfg.config_data = read_32bitBE(configs_offset,streamFile); + cfg.config_data = read_32bitBE(configs_offset,sf); //cfg.encoder_delay = 0x100; //todo not used? num_samples seems to count all data vgmstream->codec_data = init_atrac9(&cfg); @@ -434,14 +434,14 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { uint8_t buf[0x100]; int bytes, format, average_bps, block_align; - format = read_16bitBE(fsb5.extradata_offset+0x00,streamFile); - block_align = (uint16_t)read_16bitBE(fsb5.extradata_offset+0x02,streamFile); - average_bps = (uint32_t)read_32bitBE(fsb5.extradata_offset+0x04,streamFile); + format = read_16bitBE(fsb5.extradata_offset+0x00,sf); + block_align = (uint16_t)read_16bitBE(fsb5.extradata_offset+0x02,sf); + average_bps = (uint32_t)read_32bitBE(fsb5.extradata_offset+0x04,sf); /* rest: seek entries + mini seek table? */ /* XWMA encoder only does up to 6ch (doesn't use FSB multistreams for more) */ bytes = ffmpeg_make_riff_xwma(buf,0x100, format, fsb5.stream_size, vgmstream->channels, vgmstream->sample_rate, average_bps, block_align); - vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, fsb5.stream_offset,fsb5.stream_size); + vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, fsb5.stream_offset,fsb5.stream_size); if ( !vgmstream->codec_data ) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; @@ -455,11 +455,11 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { cfg.channels = vgmstream->channels; cfg.sample_rate = vgmstream->sample_rate; - cfg.setup_id = read_32bitLE(fsb5.extradata_offset,streamFile); + cfg.setup_id = read_32bitLE(fsb5.extradata_offset,sf); vgmstream->layout_type = layout_none; vgmstream->coding_type = coding_VORBIS_custom; - vgmstream->codec_data = init_vorbis_custom(streamFile, fsb5.stream_offset, VORBIS_FSB, &cfg); + vgmstream->codec_data = init_vorbis_custom(sf, fsb5.stream_offset, VORBIS_FSB, &cfg); if (!vgmstream->codec_data) goto fail; break; @@ -477,7 +477,7 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { goto fail; } - if (!vgmstream_open_stream(vgmstream,streamFile,fsb5.stream_offset)) + if (!vgmstream_open_stream(vgmstream,sf,fsb5.stream_offset)) goto fail; return vgmstream; @@ -488,15 +488,15 @@ fail: } -static layered_layout_data* build_layered_fsb5_celt(STREAMFILE *streamFile, fsb5_header* fsb5) { +static layered_layout_data* build_layered_fsb5_celt(STREAMFILE* sf, fsb5_header* fsb5) { layered_layout_data* data = NULL; - STREAMFILE* temp_streamFile = NULL; + STREAMFILE* temp_sf = NULL; int i, layers = (fsb5->channels+1) / 2; size_t interleave; - if (read_32bitBE(fsb5->stream_offset+0x00,streamFile) != 0x17C30DF3) /* FSB CELT frame ID */ + if (read_32bitBE(fsb5->stream_offset+0x00,sf) != 0x17C30DF3) /* FSB CELT frame ID */ goto fail; - interleave = 0x04+0x04+read_32bitLE(fsb5->stream_offset+0x04,streamFile); /* frame size */ + interleave = 0x04+0x04+read_32bitLE(fsb5->stream_offset+0x04,sf); /* frame size */ //todo unknown interleave for max quality odd channel streams (found in test files) /* FSB5 odd channels use 2ch+2ch...+1ch streams, and the last only goes up to 0x17a, and other @@ -533,29 +533,31 @@ static layered_layout_data* build_layered_fsb5_celt(STREAMFILE *streamFile, fsb5 goto fail; #endif - temp_streamFile = setup_fsb5_streamfile(streamFile, fsb5->stream_offset, fsb5->stream_size, layers, i, interleave); - if (!temp_streamFile) goto fail; + temp_sf = setup_fsb5_streamfile(sf, fsb5->stream_offset, fsb5->stream_size, layers, i, interleave); + if (!temp_sf) goto fail; - if (!vgmstream_open_stream(data->layers[i], temp_streamFile, 0x00)) + if (!vgmstream_open_stream(data->layers[i], temp_sf, 0x00)) goto fail; + + close_streamfile(temp_sf); + temp_sf = NULL; } /* setup layered VGMSTREAMs */ if (!setup_layout_layered(data)) goto fail; - close_streamfile(temp_streamFile); return data; fail: - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); free_layout_layered(data); return NULL; } -static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE *streamFile, fsb5_header* fsb5, off_t configs_offset, size_t configs_size) { +static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE* sf, fsb5_header* fsb5, off_t configs_offset, size_t configs_size) { layered_layout_data* data = NULL; - STREAMFILE* temp_streamFile = NULL; + STREAMFILE* temp_sf = NULL; int i, layers = (configs_size / 0x04); size_t interleave = 0; @@ -566,7 +568,7 @@ static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE *streamFile, fs /* open each layer subfile (2ch+2ch..+1/2ch) */ for (i = 0; i < layers; i++) { - uint32_t config = read_32bitBE(configs_offset + 0x04*i, streamFile); + uint32_t config = read_32bitBE(configs_offset + 0x04*i, sf); int channel_index, layer_channels; size_t frame_size; @@ -609,21 +611,23 @@ static layered_layout_data* build_layered_fsb5_atrac9(STREAMFILE *streamFile, fs goto fail; #endif - temp_streamFile = setup_fsb5_streamfile(streamFile, fsb5->stream_offset, fsb5->stream_size, layers, i, interleave); - if (!temp_streamFile) goto fail; + temp_sf = setup_fsb5_streamfile(sf, fsb5->stream_offset, fsb5->stream_size, layers, i, interleave); + if (!temp_sf) goto fail; - if (!vgmstream_open_stream(data->layers[i], temp_streamFile, 0x00)) + if (!vgmstream_open_stream(data->layers[i], temp_sf, 0x00)) goto fail; + + close_streamfile(temp_sf); + temp_sf = NULL; } /* setup layered VGMSTREAMs */ if (!setup_layout_layered(data)) goto fail; - close_streamfile(temp_streamFile); 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/fsb5_fev.c b/Frameworks/vgmstream/vgmstream/src/meta/fsb5_fev.c index ae69c0304..3919f82bb 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/fsb5_fev.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/fsb5_fev.c @@ -1,54 +1,74 @@ -#include "meta.h" -#include "../coding/coding.h" - -/* FEV+FSB5 container [Just Cause 3 (PC), Shantae: Half-Genie Hero (Switch)] */ -VGMSTREAM * init_vgmstream_fsb5_fev_bank(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE *temp_streamFile = NULL; - off_t subfile_offset, chunk_offset, first_offset = 0x0c; - size_t subfile_size, chunk_size; - - - /* checks */ - if (!check_extensions(streamFile, "bank")) - goto fail; - - if (read_32bitBE(0x00,streamFile) != 0x52494646) /* "RIFF" */ - goto fail; - if (read_32bitBE(0x08,streamFile) != 0x46455620) /* "FEV " */ - goto fail; - - /* .fev is an event format referencing various external .fsb, but FMOD can bake .fev and .fsb to - * form a .bank, which is the format we support here (regular .fev is complex and not very interesting). - * Format is RIFF with FMT (main), LIST (config) and SND (FSB5 data), we want the FSB5 offset inside LIST */ - if (!find_chunk_le(streamFile, 0x4C495354,first_offset,0, &chunk_offset,NULL)) /* "LIST" */ - goto fail; - - if (read_32bitBE(chunk_offset+0x00,streamFile) != 0x50524F4A || /* "PROJ" */ - read_32bitBE(chunk_offset+0x04,streamFile) != 0x424E4B49) /* "BNKI" */ - goto fail; /* event .fev has "OBCT" instead of "BNKI" */ - - /* inside BNKI is a bunch of LIST each with event subchunks and finally the fsb offset */ - first_offset = chunk_offset + 0x04; - if (!find_chunk_le(streamFile, 0x534E4448,first_offset,0, &chunk_offset,&chunk_size)) /* "SNDH" */ - goto fail; - - if (chunk_size != 0x0c) - goto fail; /* assuming only one FSB5 is possible */ - subfile_offset = read_32bitLE(chunk_offset+0x04,streamFile); - subfile_size = read_32bitLE(chunk_offset+0x08,streamFile); - - - temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, "fsb"); - if (!temp_streamFile) goto fail; - - vgmstream = init_vgmstream_fsb5(temp_streamFile); - close_streamfile(temp_streamFile); - - return vgmstream; - -fail: - close_streamfile(temp_streamFile); - close_vgmstream(vgmstream); - return NULL; -} +#include "meta.h" +#include "../coding/coding.h" + +/* FEV+FSB5 container [Just Cause 3 (PC), Shantae: Half-Genie Hero (Switch)] */ +VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_sf = NULL; + off_t subfile_offset, chunk_offset, first_offset = 0x0c; + size_t subfile_size, chunk_size; + + + /* checks */ + if (!check_extensions(sf, "bank")) + goto fail; + + if (read_32bitBE(0x00,sf) != 0x52494646) /* "RIFF" */ + goto fail; + if (read_32bitBE(0x08,sf) != 0x46455620) /* "FEV " */ + goto fail; + + /* .fev is an event format referencing various external .fsb, but FMOD can bake .fev and .fsb to + * form a .bank, which is the format we support here (regular .fev is complex and not very interesting). + * Format is RIFF with FMT (main), LIST (config) and SND (FSB5 data), we want the FSB5 offset inside LIST */ + if (!find_chunk_le(sf, 0x4C495354,first_offset,0, &chunk_offset,NULL)) /* "LIST" */ + goto fail; + + if (read_32bitBE(chunk_offset+0x00,sf) != 0x50524F4A || /* "PROJ" */ + read_32bitBE(chunk_offset+0x04,sf) != 0x424E4B49) /* "BNKI" */ + goto fail; /* event .fev has "OBCT" instead of "BNKI" */ + + /* inside BNKI is a bunch of LIST each with event subchunks and finally fsb offset */ + first_offset = chunk_offset + 0x04; + if (!find_chunk_le(sf, 0x534E4448,first_offset,0, &chunk_offset,&chunk_size)) /* "SNDH" */ + goto fail; + + /* 0x00: unknown (version? ex LE: 0x00080003, 0x00080005) */ + { + int banks; + + /* multiple banks is possible but rare (only seen an extra "Silence" FSB5 in Guacamelee 2 (Switch), + * which on PC is a regular subsong in the only FSB5) */ + banks = (chunk_size - 0x04) / 0x08; + VGM_ASSERT(banks > 1, "FSB5FEV: multiple banks found\n"); + + /* Could try to set stream index based on FSB subsong ranges, also fixing num_streams and stream_index + * kinda involved and hard to test so for now just ignore it and use first offset */ + + if (banks > 2) + goto fail; + if (banks == 2) { + off_t temp_offset = read_32bitLE(chunk_offset + 0x04 + 0x08*1 + 0x00,sf); + //size_t temp_size = read_32bitLE(chunk_offset + 0x04 + 0x08*1 + 0x04,sf); + + int bank_subsongs = read_32bitLE(temp_offset + 0x08,sf); + if (bank_subsongs != 1) goto fail; + } + } + + subfile_offset = read_32bitLE(chunk_offset+0x04,sf); + subfile_size = read_32bitLE(chunk_offset+0x08,sf); + + temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, "fsb"); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_fsb5(temp_sf); + close_streamfile(temp_sf); + + return vgmstream; + +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/fsb_encrypted.c b/Frameworks/vgmstream/vgmstream/src/meta/fsb_encrypted.c index 69564a451..289287b87 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/fsb_encrypted.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/fsb_encrypted.c @@ -1,164 +1,85 @@ -#include "meta.h" -#include "fsb_keys.h" - -#define FSB_KEY_MAX 128 /* probably 32 */ - -static STREAMFILE* setup_fsb_streamfile(STREAMFILE *streamFile, const uint8_t * key, size_t key_size, int is_alt); - - -/* fully encrypted FSBs */ -VGMSTREAM * init_vgmstream_fsb_encrypted(STREAMFILE * streamFile) { - VGMSTREAM * vgmstream = NULL; - - /* checks */ - /* .fsb: standard - * .fsb.xen: various Guitar Hero (X360) */ - if ( !check_extensions(streamFile, "fsb,xen") ) - goto fail; - - /* ignore non-encrypted FSB */ - if ((read_32bitBE(0x00,streamFile) & 0xFFFFFF00) == 0x46534200) /* "FSB\0" */ - goto fail; - - - /* try fsbkey + all combinations of FSB4/5 and decryption algorithms */ - { - STREAMFILE *temp_streamFile = NULL; - uint8_t key[FSB_KEY_MAX]; - size_t key_size = read_key_file(key, FSB_KEY_MAX, streamFile); - - if (key_size) { - { - temp_streamFile = setup_fsb_streamfile(streamFile, key,key_size, 0); - if (!temp_streamFile) goto fail; - - if (!vgmstream) vgmstream = init_vgmstream_fsb(temp_streamFile); - if (!vgmstream) vgmstream = init_vgmstream_fsb5(temp_streamFile); - - close_streamfile(temp_streamFile); - } - - if (!vgmstream) { - temp_streamFile = setup_fsb_streamfile(streamFile, key,key_size, 1); - if (!temp_streamFile) goto fail; - - if (!vgmstream) vgmstream = init_vgmstream_fsb(temp_streamFile); - if (!vgmstream) vgmstream = init_vgmstream_fsb5(temp_streamFile); - - close_streamfile(temp_streamFile); - } - } - } - - - /* try all keys until one works */ - if (!vgmstream) { - int i; - STREAMFILE *temp_streamFile = NULL; - - for (i = 0; i < fsbkey_list_count; i++) { - fsbkey_info entry = fsbkey_list[i]; - //;VGM_LOG("fsbkey: size=%i, is_fsb5=%i, is_alt=%i\n", entry.fsbkey_size,entry.is_fsb5, entry.is_alt); - - temp_streamFile = setup_fsb_streamfile(streamFile, entry.fsbkey, entry.fsbkey_size, entry.is_alt); - if (!temp_streamFile) goto fail; - - if (fsbkey_list[i].is_fsb5) { - vgmstream = init_vgmstream_fsb5(temp_streamFile); - } else { - vgmstream = init_vgmstream_fsb(temp_streamFile); - } - - close_streamfile(temp_streamFile); - if (vgmstream) break; - } - } - - if (!vgmstream) - goto fail; - - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} - - -typedef struct { - uint8_t key[FSB_KEY_MAX]; - size_t key_size; - int is_alt; -} fsb_decryption_data; - -/* Encrypted FSB info from guessfsb and fsbext */ -static size_t fsb_decryption_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, fsb_decryption_data* data) { - static const unsigned char reverse_bits_table[] = { /* LUT to simplify, could use some bitswap function */ - 0x00,0x80,0x40,0xC0,0x20,0xA0,0x60,0xE0,0x10,0x90,0x50,0xD0,0x30,0xB0,0x70,0xF0, - 0x08,0x88,0x48,0xC8,0x28,0xA8,0x68,0xE8,0x18,0x98,0x58,0xD8,0x38,0xB8,0x78,0xF8, - 0x04,0x84,0x44,0xC4,0x24,0xA4,0x64,0xE4,0x14,0x94,0x54,0xD4,0x34,0xB4,0x74,0xF4, - 0x0C,0x8C,0x4C,0xCC,0x2C,0xAC,0x6C,0xEC,0x1C,0x9C,0x5C,0xDC,0x3C,0xBC,0x7C,0xFC, - 0x02,0x82,0x42,0xC2,0x22,0xA2,0x62,0xE2,0x12,0x92,0x52,0xD2,0x32,0xB2,0x72,0xF2, - 0x0A,0x8A,0x4A,0xCA,0x2A,0xAA,0x6A,0xEA,0x1A,0x9A,0x5A,0xDA,0x3A,0xBA,0x7A,0xFA, - 0x06,0x86,0x46,0xC6,0x26,0xA6,0x66,0xE6,0x16,0x96,0x56,0xD6,0x36,0xB6,0x76,0xF6, - 0x0E,0x8E,0x4E,0xCE,0x2E,0xAE,0x6E,0xEE,0x1E,0x9E,0x5E,0xDE,0x3E,0xBE,0x7E,0xFE, - 0x01,0x81,0x41,0xC1,0x21,0xA1,0x61,0xE1,0x11,0x91,0x51,0xD1,0x31,0xB1,0x71,0xF1, - 0x09,0x89,0x49,0xC9,0x29,0xA9,0x69,0xE9,0x19,0x99,0x59,0xD9,0x39,0xB9,0x79,0xF9, - 0x05,0x85,0x45,0xC5,0x25,0xA5,0x65,0xE5,0x15,0x95,0x55,0xD5,0x35,0xB5,0x75,0xF5, - 0x0D,0x8D,0x4D,0xCD,0x2D,0xAD,0x6D,0xED,0x1D,0x9D,0x5D,0xDD,0x3D,0xBD,0x7D,0xFD, - 0x03,0x83,0x43,0xC3,0x23,0xA3,0x63,0xE3,0x13,0x93,0x53,0xD3,0x33,0xB3,0x73,0xF3, - 0x0B,0x8B,0x4B,0xCB,0x2B,0xAB,0x6B,0xEB,0x1B,0x9B,0x5B,0xDB,0x3B,0xBB,0x7B,0xFB, - 0x07,0x87,0x47,0xC7,0x27,0xA7,0x67,0xE7,0x17,0x97,0x57,0xD7,0x37,0xB7,0x77,0xF7, - 0x0F,0x8F,0x4F,0xCF,0x2F,0xAF,0x6F,0xEF,0x1F,0x9F,0x5F,0xDF,0x3F,0xBF,0x7F,0xFF - }; - size_t bytes_read; - int i; - - bytes_read = streamfile->read(streamfile, dest, offset, length); - - /* decrypt data (inverted bits and xor) */ - for (i = 0; i < bytes_read; i++) { - uint8_t xor = data->key[(offset + i) % data->key_size]; - uint8_t val = dest[i]; - if (data->is_alt) { - dest[i] = reverse_bits_table[val ^ xor]; - } - else { - dest[i] = reverse_bits_table[val] ^ xor; - } - } - - return bytes_read; -} - -static STREAMFILE* setup_fsb_streamfile(STREAMFILE *streamFile, const uint8_t * key, size_t key_size, int is_alt) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; - fsb_decryption_data io_data = {0}; - size_t io_data_size = sizeof(fsb_decryption_data); - - /* setup decryption with key (external) */ - if (!key_size || key_size > FSB_KEY_MAX) goto fail; - - memcpy(io_data.key, key, key_size); - io_data.key_size = key_size; - io_data.is_alt = is_alt; - - /* setup subfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, fsb_decryption_read,NULL); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,"fsb"); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - - return temp_streamFile; - -fail: - close_streamfile(temp_streamFile); - return NULL; -} +#include "meta.h" +#include "fsb_keys.h" +#include "fsb_encrypted_streamfile.h" + + +/* fully encrypted FSBs */ +VGMSTREAM* init_vgmstream_fsb_encrypted(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + + /* checks */ + /* .fsb: standard + * .fsb.xen: various Guitar Hero (X360/PC) */ + if (!check_extensions(sf, "fsb,xen")) + goto fail; + + /* ignore non-encrypted FSB */ + if ((read_u32be(0x00,sf) & 0xFFFFFF00) == 0x46534200) /* "FSB\0" */ + goto fail; + + + /* try fsbkey + all combinations of FSB4/5 and decryption algorithms */ + { + STREAMFILE* temp_sf = NULL; + uint8_t key[FSB_KEY_MAX]; + size_t key_size = read_key_file(key, FSB_KEY_MAX, sf); + + if (key_size) { + { + temp_sf = setup_fsb_streamfile(sf, key,key_size, 0); + if (!temp_sf) goto fail; + + if (!vgmstream) vgmstream = init_vgmstream_fsb(temp_sf); + if (!vgmstream) vgmstream = init_vgmstream_fsb5(temp_sf); + + close_streamfile(temp_sf); + } + + if (!vgmstream) { + temp_sf = setup_fsb_streamfile(sf, key,key_size, 1); + if (!temp_sf) goto fail; + + if (!vgmstream) vgmstream = init_vgmstream_fsb(temp_sf); + if (!vgmstream) vgmstream = init_vgmstream_fsb5(temp_sf); + + close_streamfile(temp_sf); + } + } + } + + + /* try all keys until one works */ + if (!vgmstream) { + int i; + STREAMFILE* temp_sf = NULL; + + for (i = 0; i < fsbkey_list_count; i++) { + fsbkey_info entry = fsbkey_list[i]; + //;VGM_LOG("fsbkey: size=%i, is_fsb5=%i, is_alt=%i\n", entry.fsbkey_size,entry.is_fsb5, entry.is_alt); + + temp_sf = setup_fsb_streamfile(sf, entry.fsbkey, entry.fsbkey_size, entry.is_alt); + if (!temp_sf) goto fail; + + if (fsbkey_list[i].is_fsb5) { + vgmstream = init_vgmstream_fsb5(temp_sf); + } else { + vgmstream = init_vgmstream_fsb(temp_sf); + } + + if (vgmstream) + dump_streamfile(temp_sf, 0); + + close_streamfile(temp_sf); + if (vgmstream) break; + } + } + + if (!vgmstream) + goto fail; + + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/fsb_encrypted_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/fsb_encrypted_streamfile.h new file mode 100644 index 000000000..eae35d464 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/fsb_encrypted_streamfile.h @@ -0,0 +1,73 @@ +#ifndef _FSB_ENCRYPTED_STREAMFILE_H_ +#define _FSB_ENCRYPTED_H_ + +#define FSB_KEY_MAX 128 /* probably 32 */ + + +typedef struct { + uint8_t key[FSB_KEY_MAX]; + size_t key_size; + int is_alt; +} fsb_decryption_data; + +/* Encrypted FSB info from guessfsb and fsbext */ +static size_t fsb_decryption_read(STREAMFILE* sf, uint8_t *dest, off_t offset, size_t length, fsb_decryption_data* data) { + static const unsigned char reverse_bits_table[] = { /* LUT to simplify, could use some bitswap function */ + 0x00,0x80,0x40,0xC0,0x20,0xA0,0x60,0xE0,0x10,0x90,0x50,0xD0,0x30,0xB0,0x70,0xF0, + 0x08,0x88,0x48,0xC8,0x28,0xA8,0x68,0xE8,0x18,0x98,0x58,0xD8,0x38,0xB8,0x78,0xF8, + 0x04,0x84,0x44,0xC4,0x24,0xA4,0x64,0xE4,0x14,0x94,0x54,0xD4,0x34,0xB4,0x74,0xF4, + 0x0C,0x8C,0x4C,0xCC,0x2C,0xAC,0x6C,0xEC,0x1C,0x9C,0x5C,0xDC,0x3C,0xBC,0x7C,0xFC, + 0x02,0x82,0x42,0xC2,0x22,0xA2,0x62,0xE2,0x12,0x92,0x52,0xD2,0x32,0xB2,0x72,0xF2, + 0x0A,0x8A,0x4A,0xCA,0x2A,0xAA,0x6A,0xEA,0x1A,0x9A,0x5A,0xDA,0x3A,0xBA,0x7A,0xFA, + 0x06,0x86,0x46,0xC6,0x26,0xA6,0x66,0xE6,0x16,0x96,0x56,0xD6,0x36,0xB6,0x76,0xF6, + 0x0E,0x8E,0x4E,0xCE,0x2E,0xAE,0x6E,0xEE,0x1E,0x9E,0x5E,0xDE,0x3E,0xBE,0x7E,0xFE, + 0x01,0x81,0x41,0xC1,0x21,0xA1,0x61,0xE1,0x11,0x91,0x51,0xD1,0x31,0xB1,0x71,0xF1, + 0x09,0x89,0x49,0xC9,0x29,0xA9,0x69,0xE9,0x19,0x99,0x59,0xD9,0x39,0xB9,0x79,0xF9, + 0x05,0x85,0x45,0xC5,0x25,0xA5,0x65,0xE5,0x15,0x95,0x55,0xD5,0x35,0xB5,0x75,0xF5, + 0x0D,0x8D,0x4D,0xCD,0x2D,0xAD,0x6D,0xED,0x1D,0x9D,0x5D,0xDD,0x3D,0xBD,0x7D,0xFD, + 0x03,0x83,0x43,0xC3,0x23,0xA3,0x63,0xE3,0x13,0x93,0x53,0xD3,0x33,0xB3,0x73,0xF3, + 0x0B,0x8B,0x4B,0xCB,0x2B,0xAB,0x6B,0xEB,0x1B,0x9B,0x5B,0xDB,0x3B,0xBB,0x7B,0xFB, + 0x07,0x87,0x47,0xC7,0x27,0xA7,0x67,0xE7,0x17,0x97,0x57,0xD7,0x37,0xB7,0x77,0xF7, + 0x0F,0x8F,0x4F,0xCF,0x2F,0xAF,0x6F,0xEF,0x1F,0x9F,0x5F,0xDF,0x3F,0xBF,0x7F,0xFF + }; + size_t bytes_read; + int i; + + bytes_read = read_streamfile(dest, offset, length, sf); + + /* decrypt data (inverted bits and xor) */ + for (i = 0; i < bytes_read; i++) { + uint8_t xor = data->key[(offset + i) % data->key_size]; + uint8_t val = dest[i]; + if (data->is_alt) { + dest[i] = reverse_bits_table[val ^ xor]; + } + else { + dest[i] = reverse_bits_table[val] ^ xor; + } + } + + return bytes_read; +} + +static STREAMFILE* setup_fsb_streamfile(STREAMFILE* sf, const uint8_t* key, size_t key_size, int is_alt) { + STREAMFILE* new_sf = NULL; + fsb_decryption_data io_data = {0}; + size_t io_data_size = sizeof(fsb_decryption_data); + + /* setup decryption with key (external) */ + if (!key_size || key_size > FSB_KEY_MAX) + return NULL; + + memcpy(io_data.key, key, key_size); + io_data.key_size = key_size; + io_data.is_alt = is_alt; + + /* setup subfile */ + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_streamfile_f(new_sf, &io_data,io_data_size, fsb_decryption_read,NULL); + new_sf = open_fakename_streamfile(new_sf, NULL,"fsb"); + return new_sf; +} + +#endif /* _FSB5_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h index c8de04fc4..fd1ba4765 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h @@ -337,6 +337,9 @@ static const hcakey_info hcakey_list[] = { /* I Chu EtoileStage (Android) */ {1433227444226663680}, // 13E3D8C45778A500 + /* 22/7 Ongaku no Jikan (Android) */ + {20190906}, // 00000000013416BA + /* Dragalia Lost (iOS/Android) */ {2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD diff --git a/Frameworks/vgmstream/vgmstream/src/meta/kat.c b/Frameworks/vgmstream/vgmstream/src/meta/kat.c new file mode 100644 index 000000000..2e1f48a06 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/kat.c @@ -0,0 +1,82 @@ +#include "meta.h" +#include "../coding/coding.h" + +/* .KAT - standard sound bank format used on Dreamcast */ +VGMSTREAM *init_vgmstream_kat(STREAMFILE *sf) { + VGMSTREAM *vgmstream = NULL; + uint32_t entry_offset, type, start_offset, data_size, sample_rate, channels, bit_depth, loop_start, loop_end; + int loop_flag; + int num_sounds, target_stream = sf->stream_index; + + /* checks */ + if (!check_extensions(sf, "kat")) + goto fail; + + num_sounds = read_u32le(0x00, sf); + + if (target_stream == 0) target_stream = 1; + if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds) + goto fail; + + entry_offset = 0x04 + (target_stream - 1) * 0x2c; + + type = read_u32le(entry_offset + 0x00, sf); + if (type != 0x01) /* only type 0x01 is supported, other types are MIDI, programs, etc */ + goto fail; + + bit_depth = read_u32le(entry_offset + 0x14, sf); + if (bit_depth != 4 && bit_depth != 8 && bit_depth != 16) + goto fail; + + start_offset = read_u32le(entry_offset + 0x04, sf); + data_size = read_u32le(entry_offset + 0x08, sf); + sample_rate = read_u32le(entry_offset + 0x0c, sf); + if (sample_rate > 48000) + goto fail; + + loop_flag = read_u32le(entry_offset + 0x10, sf); + loop_start = read_u32le(entry_offset + 0x1c, sf); + loop_end = read_u32le(entry_offset + 0x20, sf); + + channels = 1; /* mono only */ + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + /* fill in the vital statistics */ + vgmstream->meta_type = meta_KAT; + vgmstream->sample_rate = sample_rate; + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + vgmstream->stream_size = data_size; + vgmstream->num_streams = num_sounds; + + switch (bit_depth) { + case 4: + vgmstream->coding_type = coding_AICA_int; + vgmstream->layout_type = layout_none; + vgmstream->num_samples = yamaha_bytes_to_samples(data_size, channels); + break; + case 8: + vgmstream->coding_type = coding_PCM8; + vgmstream->layout_type = layout_none; + vgmstream->num_samples = pcm_bytes_to_samples(data_size, channels, 8); + break; + case 16: + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_none; + vgmstream->num_samples = pcm_bytes_to_samples(data_size, channels, 16); + break; + default: + goto fail; + } + + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ktsr.c b/Frameworks/vgmstream/vgmstream/src/meta/ktsr.c new file mode 100644 index 000000000..63223977c --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/ktsr.c @@ -0,0 +1,566 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "../layout/layout.h" + +typedef enum { NONE, MSADPCM, DSP, GCADPCM, ATRAC9, KVS, /*KNS*/ } ktsr_codec; + +#define MAX_CHANNELS 8 + +typedef struct { + int total_subsongs; + int target_subsong; + ktsr_codec codec; + + int platform; + int format; + + int channels; + int sample_rate; + int32_t num_samples; + int32_t loop_start; + int loop_flag; + off_t extra_offset; + uint32_t channel_layout; + + int is_external; + off_t stream_offsets[MAX_CHANNELS]; + size_t stream_sizes[MAX_CHANNELS]; + + off_t sound_name_offset; + off_t config_name_offset; + char name[255+1]; +} ktsr_header; + +static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf); +static layered_layout_data* build_layered_atrac9(ktsr_header* ktsr, STREAMFILE *sf, uint32_t config_data); + + +/* KTSR - Koei Tecmo sound resource countainer */ +VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE *sf_b = NULL; + ktsr_header ktsr = {0}; + int target_subsong = sf->stream_index; + int separate_offsets = 0; + + + /* checks */ + /* .ktsl2asbin: common [Atelier Ryza (PC/Switch), Nioh (PC)] */ + if (!check_extensions(sf, "ktsl2asbin")) + goto fail; + + /* KTSR can be a memory file (ktsl2asbin), streams (ktsl2stbin) and global config (ktsl2gcbin) + * This accepts ktsl2asbin with internal data, or opening external streams as subsongs. + * Some info from KTSR.bt */ + + if (read_u32be(0x00, sf) != 0x4B545352) /* "KTSR" */ + goto fail; + if (read_u32be(0x04, sf) != 0x777B481A) /* hash(?) id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */ + goto fail; + + if (target_subsong == 0) target_subsong = 1; + ktsr.target_subsong = target_subsong; + + if (!parse_ktsr(&ktsr, sf)) + goto fail; + + /* open companion body */ + if (ktsr.is_external) { + sf_b = open_streamfile_by_ext(sf, "ktsl2stbin"); + if (!sf_b) { + VGM_LOG("KTSR: companion file not found\n"); + goto fail; + } + } + else { + sf_b = sf; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(ktsr.channels, ktsr.loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_KTSR; + vgmstream->sample_rate = ktsr.sample_rate; + vgmstream->num_samples = ktsr.num_samples; + vgmstream->loop_start_sample = ktsr.loop_start; + vgmstream->loop_end_sample = ktsr.num_samples; + vgmstream->stream_size = ktsr.stream_sizes[0]; + vgmstream->num_streams = ktsr.total_subsongs; + vgmstream->channel_layout = ktsr.channel_layout; + strcpy(vgmstream->stream_name, ktsr.name); + + switch(ktsr.codec) { + + case MSADPCM: + vgmstream->coding_type = coding_MSADPCM_int; + vgmstream->layout_type = layout_none; + separate_offsets = 1; + + /* 0x00: samples per frame */ + vgmstream->frame_size = read_u16le(ktsr.extra_offset + 0x02, sf_b); + break; + + case DSP: + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_none; + separate_offsets = 1; + + dsp_read_coefs_le(vgmstream, sf, ktsr.extra_offset + 0x1c, 0x60); + dsp_read_hist_le (vgmstream, sf, ktsr.extra_offset + 0x40, 0x60); + break; + +#ifdef VGM_USE_ATRAC9 + case ATRAC9: { + /* 0x00: samples per frame */ + /* 0x02: frame size */ + uint32_t config_data = read_u32be(ktsr.extra_offset + 0x04, sf); + if ((config_data & 0xFF) == 0xFE) /* later versions(?) in LE */ + config_data = read_u32le(ktsr.extra_offset + 0x04, sf); + + vgmstream->layout_data = build_layered_atrac9(&ktsr, sf_b, config_data); + if (!vgmstream->layout_data) goto fail; + vgmstream->layout_type = layout_layered; + vgmstream->coding_type = coding_ATRAC9; + break; + +#if 0 + atrac9_config cfg = {0}; + if (ktsr.channels > 1) { + VGM_LOG("1\n"); + goto fail; + } + + /* 0x00: samples per frame */ + /* 0x02: frame size */ + cfg.config_data = read_u32be(ktsr.extra_offset + 0x04, sf_b); + if ((cfg.config_data & 0xFF) == 0xFE) /* later versions(?) in LE */ + cfg.config_data = read_u32le(ktsr.extra_offset + 0x04, sf_b); + + cfg.channels = vgmstream->channels; + cfg.encoder_delay = 256; /* observed default (ex. Attack on Titan PC vs Vita) */ + + vgmstream->codec_data = init_atrac9(&cfg); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_ATRAC9; + vgmstream->layout_type = layout_none; + break; +#endif + } +#endif + +#ifdef VGM_USE_VORBIS + case KVS: { + VGMSTREAM *ogg_vgmstream = NULL; //TODO: meh + STREAMFILE *sf_kvs = setup_subfile_streamfile(sf_b, ktsr.stream_offsets[0], ktsr.stream_sizes[0], "kvs"); + if (!sf_kvs) goto fail; + + ogg_vgmstream = init_vgmstream_ogg_vorbis(sf_kvs); + close_streamfile(sf_kvs); + if (ogg_vgmstream) { + ogg_vgmstream->stream_size = vgmstream->stream_size; + ogg_vgmstream->num_streams = vgmstream->num_streams; + ogg_vgmstream->channel_layout = vgmstream->channel_layout; + /* loops look shared */ + strcpy(ogg_vgmstream->stream_name, vgmstream->stream_name); + + close_vgmstream(vgmstream); + if (sf_b != sf) close_streamfile(sf_b); + return ogg_vgmstream; + } + else { + goto fail; + } + + break; + } +#endif + + default: + goto fail; + } + + + if (!vgmstream_open_stream_bf(vgmstream, sf_b, ktsr.stream_offsets[0], 1)) + goto fail; + + + /* data offset per channel is absolute (not actual interleave since there is padding) in some cases */ + if (separate_offsets) { + int i; + for (i = 0; i < ktsr.channels; i++) { + vgmstream->ch[i].offset = ktsr.stream_offsets[i]; + } + } + + if (sf_b != sf) close_streamfile(sf_b); + return vgmstream; + +fail: + if (sf_b != sf) close_streamfile(sf_b); + close_vgmstream(vgmstream); + return NULL; +} + +static layered_layout_data* build_layered_atrac9(ktsr_header* ktsr, STREAMFILE* sf, uint32_t config_data) { + STREAMFILE* temp_sf = NULL; + layered_layout_data* data = NULL; + int layers = ktsr->channels; + int i; + + + /* init layout */ + data = init_layout_layered(layers); + if (!data) goto fail; + + for (i = 0; i < layers; i++) { + data->layers[i] = allocate_vgmstream(1, 0); + if (!data->layers[i]) goto fail; + + data->layers[i]->sample_rate = ktsr->sample_rate; + data->layers[i]->num_samples = ktsr->num_samples; + +#ifdef VGM_USE_ATRAC9 + { + atrac9_config cfg = {0}; + + cfg.config_data = config_data; + cfg.channels = 1; + cfg.encoder_delay = 256; /* observed default (ex. Attack on Titan PC vs Vita) */ + + data->layers[i]->codec_data = init_atrac9(&cfg); + if (!data->layers[i]->codec_data) goto fail; + data->layers[i]->coding_type = coding_ATRAC9; + data->layers[i]->layout_type = layout_none; + } +#else + goto fail; +#endif + + temp_sf = setup_subfile_streamfile(sf, ktsr->stream_offsets[i], ktsr->stream_sizes[i], NULL); + if (!temp_sf) goto fail; + + if (!vgmstream_open_stream(data->layers[i], temp_sf, 0x00)) + goto fail; + + close_streamfile(temp_sf); + temp_sf = NULL; + } + + /* setup layered VGMSTREAMs */ + if (!setup_layout_layered(data)) + goto fail; + return data; +fail: + close_streamfile(temp_sf); + free_layout_layered(data); + return NULL; +} + + +static int parse_codec(ktsr_header* ktsr) { + + /* platform + format to codec, simplified until more codec combos are found */ + switch(ktsr->platform) { + case 0x01: /* PC */ + if (ktsr->is_external) + ktsr->codec = KVS; + else if (ktsr->format == 0x00) + ktsr->codec = MSADPCM; + else + goto fail; + break; + + case 0x03: /* VITA */ + if (ktsr->is_external) + goto fail; + else if (ktsr->format == 0x01) + ktsr->codec = ATRAC9; + else + goto fail; + break; + + case 0x04: /* Switch */ + if (ktsr->is_external) + goto fail; /* KTSS? */ + else if (ktsr->format == 0x00) + ktsr->codec = DSP; + else + goto fail; + break; + + default: + goto fail; + } + + return 1; +fail: + VGM_LOG("KTSR: unknown codec combo: ext=%x, fmt=%x, ptf=%x\n", ktsr->is_external, ktsr->format, ktsr->platform); + return 0; +} + +static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) { + off_t suboffset, starts_offset, sizes_offset; + int i; + uint32_t type; + + type = read_u32be(offset + 0x00, sf); + //size = read_u32le(offset + 0x04, sf); + + /* probably could check the flag in sound header, but the format is kinda messy */ + switch(type) { /* hash-id? */ + + case 0x38D0437D: /* external [Nioh (PC), Atelier Ryza (PC)] */ + /* 08 subtype? (ex. 0x522B86B9) + * 0c channels + * 10 ? (always 0x002706B8) + * 14 codec? (05=KVS) + * 18 sample rate + * 1c num samples + * 20 null? + * 24 loop start or -1 (loop end is num samples) + * 28 channel layout (or null?) + * 2c null + * 30 null + * 34 data offset (absolute to external stream, points to actual format and not to mini-header) + * 38 data size + * 3c always 0x0200 + */ + + ktsr->channels = read_u32le(offset + 0x0c, sf); + ktsr->format = read_u32le(offset + 0x14, sf); + /* other fields will be read in the external stream */ + + ktsr->channel_layout= read_u32le(offset + 0x28, sf); + + ktsr->stream_offsets[0] = read_u32le(offset + 0x34, sf); + ktsr->stream_sizes[0] = read_u32le(offset + 0x38, sf); + ktsr->is_external = 1; + + if (ktsr->format != 0x05) { + VGM_LOG("KTSR: unknown subcodec at %lx\n", offset); + goto fail; + } + + break; + + case 0x41FDBD4E: /* internal [Attack on Titan: Wings of Freedom (Vita)] */ + case 0x6FF273F9: /* internal [Attack on Titan: Wings of Freedom (PC/Vita)] */ + case 0x6FCAB62E: /* internal [Marvel Ultimate Alliance 3: The Black Order (Switch)] */ + case 0x6AD86FE9: /* internal [Atelier Ryza (PC/Switch), Persona5 Scramble (Switch)] */ + case 0x10250527: /* internal [Fire Emblem: Three Houses DLC (Switch)] */ + /* 08 subtype? (0x6029DBD2, 0xD20A92F90, 0xDC6FF709) + * 0c channels + * 10 format? (00=platform's ADPCM? 01=ATRAC9?) + * 11 bps? (always 16) + * 12 null + * 14 sample rate + * 18 num samples + * 1c null or 0x100? + * 20 loop start or -1 (loop end is num samples) + * 24 channel layout or null + * 28 header offset (within subfile) + * 2c header size [B, C] + * 30 offset to data start offset [A, C] or to data start+size [B] + * 34 offset to data size [A, C] or same per channel + * 38 always 0x0200 + * -- header + * -- data start offset + * -- data size + */ + + ktsr->channels = read_u32le(offset + 0x0c, sf); + ktsr->format = read_u8 (offset + 0x10, sf); + ktsr->sample_rate = read_s32le(offset + 0x14, sf); + ktsr->num_samples = read_s32le(offset + 0x18, sf); + ktsr->loop_start = read_s32le(offset + 0x20, sf); + ktsr->channel_layout= read_u32le(offset + 0x24, sf); + ktsr->extra_offset = read_u32le(offset + 0x28, sf) + offset; + if (type == 0x41FDBD4E || type == 0x6FF273F9) /* v1 */ + suboffset = offset + 0x2c; + else + suboffset = offset + 0x30; + + if (ktsr->channels > MAX_CHANNELS) { + VGM_LOG("KTSR: max channels found\n"); + goto fail; + } + + starts_offset = read_u32le(suboffset + 0x00, sf) + offset; + sizes_offset = read_u32le(suboffset + 0x04, sf) + offset; + for (i = 0; i < ktsr->channels; i++) { + ktsr->stream_offsets[i] = read_u32le(starts_offset + 0x04*i, sf) + offset; + ktsr->stream_sizes[i] = read_u32le(sizes_offset + 0x04*i, sf); + } + + ktsr->loop_flag = (ktsr->loop_start >= 0); + + break; + + default: + /* streams also have their own chunks like 0x09D4F415, not needed here */ + VGM_LOG("KTSR: unknown subheader at %lx\n", offset); + goto fail; + } + + if (!parse_codec(ktsr)) + goto fail; + + return 1; +fail: + VGM_LOG("KTSR: error parsing subheader\n"); + return 0; +} + +static void build_name(ktsr_header* ktsr, STREAMFILE* sf) { + char sound_name[255] = {0}; + char config_name[255] = {0}; + + /* names can be different or same but usually config is better */ + if (ktsr->sound_name_offset) { + read_string(sound_name, sizeof(sound_name), ktsr->sound_name_offset, sf); + } + if (ktsr->config_name_offset) { + read_string(config_name, sizeof(config_name), ktsr->config_name_offset, sf); + } + + //if (longname[0] && shortname[0]) { + // snprintf(ktsr->name, sizeof(ktsr->name), "%s; %s", longname, shortname); + //} + if (config_name[0]) { + snprintf(ktsr->name, sizeof(ktsr->name), "%s", config_name); + + } + else if (sound_name[0]) { + snprintf(ktsr->name, sizeof(ktsr->name), "%s", sound_name); + } + +} + +static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id) { + /* more configs than sounds is possible so we need target_id first */ + off_t offset, end, name_offset; + uint32_t stream_id; + + offset = 0x40; + end = get_streamfile_size(sf); + while (offset < end) { + uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */ + uint32_t size = read_u32le(offset + 0x04, sf); + switch(type) { + case 0xBD888C36: /* config */ + stream_id = read_u32be(offset + 0x08, sf); + if (stream_id != target_id) + break; + + name_offset = read_u32le(offset + 0x28, sf); + if (name_offset > 0) + ktsr->config_name_offset = offset + name_offset; + return; /* id found */ + + default: + break; + } + + offset += size; + } + +} + +static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { + off_t offset, end, header_offset, name_offset; + uint32_t stream_id = 0, stream_count; + + /* 00: KTSR + * 04: type + * 08: version? + * 0a: unknown (usually 00, 02/03 seen in Vita) + * 0b: platform (01=PC, 03=Vita, 04=Switch) + * 0c: game id? + * 10: null + * 14: null + * 18: file size + * 1c: file size + * up to 40: reserved + * until end: entries (totals not defined) */ + + ktsr->platform = read_u8(0x0b,sf); + + if (read_u32le(0x18, sf) != read_u32le(0x1c, sf)) + goto fail; + if (read_u32le(0x1c, sf) != get_streamfile_size(sf)) + goto fail; + + offset = 0x40; + end = get_streamfile_size(sf); + while (offset < end) { + uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */ + uint32_t size = read_u32le(offset + 0x04, sf); + + /* parse chunk-like subfiles, usually N configs then N songs */ + switch(type) { + case 0x6172DBA8: /* padding (empty) */ + case 0xBD888C36: /* config (floats, stream id, etc, may have extended name) */ + case 0xC9C48EC1: /* unknown (has some string inside like "boss") */ + break; + + case 0xC5CCCB70: /* sound (internal data or external stream) */ + //VGM_LOG("info at %lx\n", offset); + ktsr->total_subsongs++; + + /* sound table: + * 08: stream id (used in several places) + * 0c: unknown (low number but not version?) + * 0e: external flag + * 10: sub-streams? + * 14: offset to header offset + * 18: offset to name + * --: name + * --: header offset + * --: header + * --: subheader (varies) */ + + + if (ktsr->total_subsongs == ktsr->target_subsong) { + //;VGM_LOG("KTSR: target at %lx\n", offset); + + stream_id = read_u32be(offset + 0x08,sf); + //ktsr->is_external = read_u16le(offset + 0x0e,sf); + stream_count = read_u32le(offset + 0x10,sf); + if (stream_count != 1) { + VGM_LOG("KTSR: unknown stream count\n"); + goto fail; + } + + header_offset = read_u32le(offset + 0x14, sf); + name_offset = read_u32le(offset + 0x18, sf); + if (name_offset > 0) + ktsr->sound_name_offset = offset + name_offset; + + header_offset = read_u32le(offset + header_offset, sf) + offset; + + if (!parse_ktsr_subfile(ktsr, sf, header_offset)) + goto fail; + } + break; + + default: + /* streams also have their own chunks like 0x09D4F415, not needed here */ + VGM_LOG("KTSR: unknown chunk at %lx\n", offset); + goto fail; + } + + offset += size; + } + + if (ktsr->target_subsong > ktsr->total_subsongs) + goto fail; + + parse_longname(ktsr, sf, stream_id); + build_name(ktsr, sf); + + return 1; +fail: + return 0; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/meta.h b/Frameworks/vgmstream/vgmstream/src/meta/meta.h index 5ba507cdd..27e4d3d31 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/meta.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/meta.h @@ -137,7 +137,6 @@ VGMSTREAM * init_vgmstream_hca_subkey(STREAMFILE *streamFile, uint16_t subkey); #ifdef VGM_USE_FFMPEG VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size); VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE * streamFile); #endif @@ -654,7 +653,10 @@ VGMSTREAM * init_vgmstream_naac(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_ubi_dat(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ubi_bnm(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_ubi_bnm_ps2(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_ubi_blk(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ezw(STREAMFILE * streamFile); @@ -897,4 +899,12 @@ VGMSTREAM* init_vgmstream_diva(STREAMFILE* sf); VGMSTREAM* init_vgmstream_imuse(STREAMFILE* sf); +VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf); + +VGMSTREAM* init_vgmstream_mups(STREAMFILE* sf); + +VGMSTREAM* init_vgmstream_kat(STREAMFILE* sf); + +VGMSTREAM* init_vgmstream_pcm_success(STREAMFILE* sf); + #endif /*_META_H*/ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mups.c b/Frameworks/vgmstream/vgmstream/src/meta/mups.c new file mode 100644 index 000000000..fbfa5c65e --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/mups.c @@ -0,0 +1,42 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "mups_streamfile.h" + + +/* MUPS - from Watermelon/HUCARD games (same programmer) [Pier Solar and the Great Architects (PC), Ghost Blade HD (PC/Switch)] */ +VGMSTREAM* init_vgmstream_mups(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE *temp_sf = NULL; + + + /* checks */ + /* mups: header id? + * (extensionless): default? */ + if (!check_extensions(sf, "mups,")) + goto fail; + + if (read_u32be(0x00,sf) != 0x4D555053) /* "MUPS" */ + goto fail; + if (read_u32be(0x08,sf) != 0x50737348) /* "PssH" */ + goto fail; + + /* just an Ogg with changed OggS/vorbis words (see streamfile) */ + temp_sf = setup_mups_streamfile(sf, 0x08); + if (!temp_sf) goto fail; + +#ifdef VGM_USE_VORBIS + vgmstream = init_vgmstream_ogg_vorbis(temp_sf); + if (!vgmstream) goto fail; +#else + goto fail; +#endif + + close_streamfile(temp_sf); + + return vgmstream; + +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mups_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/mups_streamfile.h new file mode 100644 index 000000000..411cf885b --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/mups_streamfile.h @@ -0,0 +1,102 @@ +#ifndef _MUPS_STREAMFILE_H_ +#define _MUPS_STREAMFILE_H_ +#include "deblock_streamfile.h" + +static inline int32_t max32(int32_t val1, int32_t val2) { + if (val1 > val2) + return val2; + return val1; +} + +static void read_callback(uint8_t* dst, deblock_io_data* data, size_t block_pos, size_t read_size) { + static const uint8_t oggs[] = { 0x4F, 0x67, 0x67, 0x53 }; + static const uint8_t vorbis[] = { 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73 }; + int i, min, max; + + /* Swaps Xiph magic words back (resulting page checksum is ok). + * Reads can start/end anywhere, but block_pos = 0 is always page start */ + + /* change "PssH" back to "OggS" */ + if (block_pos < 0x04) { + min = block_pos; + if (min < 0x00) + min = 0x00; + + max = block_pos + read_size; + if (max > 0x04) + max = 0x04; + + for (i = min; i < max; i++) { + dst[i] = oggs[i - 0x00]; + } + } + + /* first page also needs "psolar" to "vorbis" */ + if (data->logical_offset == 0 && block_pos < 0x23) { + min = block_pos; + if (min < 0x1d) + min = 0x1d; + + max = block_pos + read_size; + if (max > 0x23) + max = 0x23; + + for (i = min; i < max; i++) { + dst[i] = vorbis[i - 0x1d]; + } + } +} + +static int get_page_size(STREAMFILE* sf, off_t page_offset) { + static const int base_size = 0x1b; + uint8_t page[0x1b + 0x100]; + uint8_t segments; + size_t page_size; + int i, bytes; + + bytes = read_streamfile(page + 0x00, page_offset + 0x00, base_size, sf); + if (bytes != base_size) goto fail; + + if (get_u32be(page + 0x00) != 0x50737348) /* "PssH" */ + goto fail; + + segments = get_u8(page + 0x1a); + + bytes = read_streamfile(page + base_size, page_offset + base_size, segments, sf); + if (bytes != segments) goto fail; + + page_size = base_size + segments; + for (i = 0; i < segments; i++) { + uint8_t segment_size = get_u8(page + base_size + i); + page_size += segment_size; + } + + return page_size; +fail: + return -1; /* not a valid page */ +} + +static void block_callback(STREAMFILE* sf, deblock_io_data* data) { + off_t page_offset = data->physical_offset; + + /* block size = OggS page size as we need read_callback called on page starts */ + data->data_size = get_page_size(sf, page_offset); + data->block_size = data->data_size; +} + +/* Fixes MUPS streams that contain mutated OggS */ +static STREAMFILE* setup_mups_streamfile(STREAMFILE* sf, off_t stream_offset) { + STREAMFILE* new_sf = NULL; + deblock_config_t cfg = {0}; + + cfg.stream_start = stream_offset; + cfg.block_callback = block_callback; + cfg.read_callback = read_callback; + + new_sf = open_wrap_streamfile(sf); + new_sf = open_io_deblock_streamfile_f(new_sf, &cfg); + new_sf = open_fakename_streamfile_f(new_sf, NULL, "ogg"); + return new_sf; +} + +#endif /* _MUPS_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c b/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c index e531c890c..b1a9e760e 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c @@ -655,48 +655,6 @@ fail: return NULL; } -/* sadf - Procyon Studio Header Variant [Xenoblade Chronicles 2 (Switch)] (sfx) */ -VGMSTREAM * init_vgmstream_sadf(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - int channel_count, loop_flag; - off_t start_offset; - - /* checks */ - if (!check_extensions(streamFile, "sad")) - goto fail; - if (read_32bitBE(0x00, streamFile) != 0x73616466) /* "sadf" */ - goto fail; - - channel_count = read_8bit(0x18, streamFile); - loop_flag = read_8bit(0x19, streamFile); - start_offset = read_32bitLE(0x1C, streamFile); - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); - if (!vgmstream) goto fail; - - vgmstream->num_samples = read_32bitLE(0x28, streamFile); - vgmstream->sample_rate = read_32bitLE(0x24, streamFile); - if (loop_flag) { - vgmstream->loop_start_sample = read_32bitLE(0x2c, streamFile); - vgmstream->loop_end_sample = read_32bitLE(0x30, streamFile); - } - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = channel_count == 1 ? 0x8 : - read_32bitLE(0x20, streamFile) / channel_count; - vgmstream->meta_type = meta_DSP_SADF; - - dsp_read_coefs_le(vgmstream, streamFile, 0x80, 0x80); - - if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} /* IDSP - Traveller's Tales header + interleaved dsps [Lego Batman (Wii), Lego Dimensions (Wii U)] */ VGMSTREAM * init_vgmstream_idsp_tt(STREAMFILE *streamFile) { diff --git a/Frameworks/vgmstream/vgmstream/src/meta/pcm_success.c b/Frameworks/vgmstream/vgmstream/src/meta/pcm_success.c new file mode 100644 index 000000000..e4c2bd4d4 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/pcm_success.c @@ -0,0 +1,68 @@ +#include "meta.h" +#include "../coding/coding.h" + + +/* PCM - from Success (related) games [Metal Saga (PS2), Tetris Kiwamemichi (PS2), Duel Masters: Rebirth of Super Dragon (PS2)] */ +VGMSTREAM* init_vgmstream_pcm_success(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + off_t start_offset; + int loop_flag, channels, sample_rate, interleave; + size_t data_size, loop_start, loop_end, loop_adjust; + + + /* checks */ + if (!check_extensions(sf, "pcm")) + goto fail; + + if (read_u32be(0x00,sf) != 0x50434D20) /* "PCM " */ + goto fail; + if (read_u32le(0x04,sf) != 0x00010000) /* version? */ + goto fail; + if (read_u32le(0x08,sf) + 0x8000 < get_streamfile_size(sf)) /* data size without padding */ + goto fail; + + interleave = 0x800; + start_offset = 0x800; + + sample_rate = read_s32le(0x0c,sf); + channels = read_s32le(0x10,sf); + loop_flag = read_s32le(0x14,sf); + + data_size = read_s32le(0x18,sf) * interleave * channels; + /* loops seems slightly off, so 'adjust' meaning may need to be tweaked */ + loop_adjust = read_s32le(0x1c,sf) * channels; /* from 0..<0x800 */ + loop_start = read_s32le(0x20,sf) * interleave * channels + loop_adjust; + loop_adjust = read_s32le(0x24,sf) * channels; /* always 0x800 (0 if no loop flag) */ + loop_end = read_s32le(0x28,sf) * interleave * channels + (interleave * channels - loop_adjust); + + /* 0x2c: always 1? */ + /* 0x30/40: padding garbage (also at file end) */ + + /* not always accurate and has padding */ + if (data_size > get_streamfile_size(sf) - start_offset) + data_size = get_streamfile_size(sf) - start_offset; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_PCM_SUCCESS; + vgmstream->sample_rate = sample_rate; + + vgmstream->num_samples = ps_bytes_to_samples(data_size, channels); + vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channels); + vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channels); + + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = interleave; + + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps2_joe.c b/Frameworks/vgmstream/vgmstream/src/meta/ps2_joe.c index dda438df5..c3eea49d4 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ps2_joe.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ps2_joe.c @@ -8,7 +8,7 @@ VGMSTREAM * init_vgmstream_ps2_joe(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int channel_count, loop_flag, sample_rate; - int32_t num_samples, loop_start = 0, loop_end = 0; + int32_t num_samples; size_t file_size, data_size, unknown1, unknown2, interleave, padding_size; @@ -65,8 +65,6 @@ VGMSTREAM * init_vgmstream_ps2_joe(STREAMFILE *streamFile) { vgmstream->sample_rate = sample_rate; vgmstream->num_samples = num_samples; - vgmstream->loop_start_sample = loop_start; - vgmstream->loop_end_sample = loop_end; vgmstream->stream_size = data_size; vgmstream->coding_type = coding_PSX; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/riff.c b/Frameworks/vgmstream/vgmstream/src/meta/riff.c index e96939a5d..53eba94e2 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/riff.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/riff.c @@ -132,9 +132,10 @@ static int read_fmt(int big_endian, STREAMFILE * streamFile, off_t current_chunk } switch (fmt->codec) { - case 0x00: /* Yamaha AICA ADPCM [Headhunter (DC), Bomber hehhe (DC)] (unofficial) */ + case 0x00: /* Yamaha AICA ADPCM [Headhunter (DC), Bomber hehhe (DC), Rayman 2 (DC)] (unofficial) */ if (fmt->bps != 4) goto fail; - if (fmt->block_size != 0x02*fmt->channel_count) goto fail; + if (fmt->block_size != 0x02*fmt->channel_count && + fmt->block_size != 0x01*fmt->channel_count) goto fail; fmt->coding_type = coding_AICA_int; fmt->interleave = 0x01; break; @@ -370,14 +371,25 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { uint16_t codec = read_16bitLE(0x14,streamFile); if (riff_size+0x08+0x01 == file_size) riff_size += 0x01; /* [Shikkoku no Sharnoth (PC)] */ + else if (riff_size == file_size && codec == 0x0069) riff_size -= 0x08; /* [Dynasty Warriors 3 (Xbox), BloodRayne (Xbox)] */ - else if (riff_size + 0x04 == file_size && codec == 0x0000) - riff_size -= 0x04; /* [Headhunter (DC), Bomber hehhe (DC)] */ + else if (riff_size + 0x04 == file_size && codec == 0x0069) riff_size -= 0x04; /* [Halo 2 (PC)] (possibly bad extractor? 'Gravemind Tool') */ + + else if (riff_size + 0x04 == file_size && codec == 0x0000) + riff_size -= 0x04; /* [Headhunter (DC), Bomber hehhe (DC)] */ + + else if (riff_size == file_size && codec == 0x0000) + riff_size -= 0x08; /* [Rayman 2 (DC)] */ + + else if (riff_size + 0x02 + 0x08 == file_size && codec == 0x0000) + riff_size -= 0x02; /* [Rayman 2 (DC)]-dcz */ + else if (riff_size == file_size && codec == 0x0300) riff_size -= 0x08; /* [Chrono Ma:gia (Android)] */ + else if (riff_size >= file_size && read_32bitBE(0x24,streamFile) == 0x4E584246) /* "NXBF" */ riff_size = file_size - 0x08; /* [R:Racing Evolution (Xbox)] */ } @@ -405,7 +417,7 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { if (!read_fmt(0, streamFile, current_chunk, &fmt, mwv)) goto fail; - /* some Dreamcast/Naomi games again [Headhunter (DC), Bomber hehhe (DC)] */ + /* some Dreamcast/Naomi games again [Headhunter (DC), Bomber hehhe (DC), Rayman 2 (DC)] */ if (fmt.codec == 0x0000 && chunk_size == 0x12) chunk_size += 0x02; break; @@ -924,6 +936,7 @@ static size_t get_ue4_msadpcm_interleave(STREAMFILE *sf, riff_fmt_chunk *fmt, of return v2_interleave; /* favor newer games */ } +/* same but big endian, seen in the spec and in Kitchenette (PC) */ VGMSTREAM * init_vgmstream_rifx(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; riff_fmt_chunk fmt = {0}; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sadf.c b/Frameworks/vgmstream/vgmstream/src/meta/sadf.c new file mode 100644 index 000000000..6cbc91319 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/sadf.c @@ -0,0 +1,51 @@ +#include "meta.h" +#include "../coding/coding.h" + +/* sadf - from Procyon Studio audio driver games [Xenoblade Chronicles 2 (Switch)] (sfx) */ +VGMSTREAM* init_vgmstream_sadf(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + int channel_count, loop_flag; + off_t start_offset; + + + /* checks */ + /* .sad: assumed (from older sadX formats) + * .nop: assumed (from streamed files) + * (extensionless): name in .xsp bigfiles */ + if (!check_extensions(sf, "sad,nop,")) + goto fail; + + if (read_32bitBE(0x00, sf) != 0x73616466) /* "sadf" */ + goto fail; + if (read_32bitBE(0x08, sf) != 0x6470636D) /* "dpcm" ("opus" is used too, see opus.c, "ipcm" supposedly too) */ + goto fail; + + channel_count = read_8bit(0x18, sf); + loop_flag = read_8bit(0x19, sf); + start_offset = read_32bitLE(0x1C, sf); + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->num_samples = read_32bitLE(0x28, sf); + vgmstream->sample_rate = read_32bitLE(0x24, sf); + if (loop_flag) { + vgmstream->loop_start_sample = read_32bitLE(0x2c, sf); + vgmstream->loop_end_sample = read_32bitLE(0x30, sf); + } + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = channel_count == 1 ? 0x8 : read_32bitLE(0x20, sf) / channel_count; + vgmstream->meta_type = meta_SADF; + + dsp_read_coefs_le(vgmstream, sf, 0x80, 0x80); + + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/nds_sad.c b/Frameworks/vgmstream/vgmstream/src/meta/sadl.c similarity index 53% rename from Frameworks/vgmstream/vgmstream/src/meta/nds_sad.c rename to Frameworks/vgmstream/vgmstream/src/meta/sadl.c index 944cf04b0..98132b783 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/nds_sad.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/sadl.c @@ -1,32 +1,30 @@ #include "meta.h" -#include "../util.h" -/* sadl - from DS games with Procyon Studio audio driver */ -VGMSTREAM * init_vgmstream_sadl(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; +/* sadl - from DS games with Procyon Studio audio driver [Professor Layton (DS), Soma Bringer (DS)] */ +VGMSTREAM* init_vgmstream_sadl(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + int channel_count, loop_flag; off_t start_offset; - int loop_flag, channel_count; /* checks */ - if (!check_extensions(streamFile, "sad")) + if (!check_extensions(sf, "sad")) goto fail; - if (read_32bitBE(0x00,streamFile) != 0x7361646c) /* "sadl" */ + if (read_32bitBE(0x00,sf) != 0x7361646c) /* "sadl" */ goto fail; - if (read_32bitLE(0x40,streamFile) != get_streamfile_size(streamFile)) + if (read_32bitLE(0x40,sf) != get_streamfile_size(sf)) goto fail; - - loop_flag = read_8bit(0x31,streamFile); - channel_count = read_8bit(0x32,streamFile); + loop_flag = read_8bit(0x31,sf); + channel_count = read_8bit(0x32,sf); start_offset = 0x100; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; - switch (read_8bit(0x33,streamFile) & 6) { + switch (read_8bit(0x33,sf) & 6) { case 4: vgmstream->sample_rate = 32728; break; @@ -42,20 +40,20 @@ VGMSTREAM * init_vgmstream_sadl(STREAMFILE *streamFile) { vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x10; - switch(read_8bit(0x33,streamFile) & 0xf0) { + switch(read_8bit(0x33,sf) & 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->num_samples = (read_32bitLE(0x40,sf)-start_offset)/channel_count*2; + vgmstream->loop_start_sample = (read_32bitLE(0x54,sf)-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->num_samples = (read_32bitLE(0x40,sf)-start_offset)/channel_count/16*30; + vgmstream->loop_start_sample = (read_32bitLE(0x54,sf)-start_offset)/channel_count/16*30; vgmstream->loop_end_sample = vgmstream->num_samples; break; @@ -63,7 +61,7 @@ VGMSTREAM * init_vgmstream_sadl(STREAMFILE *streamFile) { goto fail; } - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) goto fail; return vgmstream; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c index 7d44c6353..ac6855285 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c @@ -1473,19 +1473,26 @@ static STREAMFILE * setup_bao_streamfile(ubi_bao_header *bao, STREAMFILE *stream if (!new_streamFile) goto fail; stream_segments[0] = new_streamFile; - new_streamFile = open_atomic_bao(bao->cfg.file_type, bao->stream_id, 1, streamFile); - if (!new_streamFile) goto fail; - stream_segments[1] = new_streamFile; + if (bao->stream_size - bao->prefetch_size != 0) { + new_streamFile = open_atomic_bao(bao->cfg.file_type, bao->stream_id, 1, streamFile); + if (!new_streamFile) goto fail; + stream_segments[1] = new_streamFile; - new_streamFile = open_clamp_streamfile(stream_segments[1], bao->stream_offset, (bao->stream_size - bao->prefetch_size)); - if (!new_streamFile) goto fail; - stream_segments[1] = new_streamFile; + new_streamFile = open_clamp_streamfile(stream_segments[1], bao->stream_offset, (bao->stream_size - bao->prefetch_size)); + if (!new_streamFile) goto fail; + stream_segments[1] = new_streamFile; - new_streamFile = open_multifile_streamfile(stream_segments, 2); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - stream_segments[0] = NULL; - stream_segments[1] = NULL; + new_streamFile = open_multifile_streamfile(stream_segments, 2); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + stream_segments[0] = NULL; + stream_segments[1] = NULL; + } + else { + /* weird but happens, streamed chunk is empty in this case */ + temp_streamFile = new_streamFile; + stream_segments[0] = NULL; + } } else { new_streamFile = open_atomic_bao(bao->cfg.file_type, bao->stream_id, bao->is_external, streamFile); @@ -1507,20 +1514,27 @@ static STREAMFILE * setup_bao_streamfile(ubi_bao_header *bao, STREAMFILE *stream if (!new_streamFile) goto fail; stream_segments[0] = new_streamFile; - new_streamFile = open_streamfile_by_filename(streamFile, bao->resource_name); - if (!new_streamFile) { VGM_LOG("UBI BAO: external stream '%s' not found\n", bao->resource_name); goto fail; } - stream_segments[1] = new_streamFile; + if (bao->stream_size - bao->prefetch_size != 0) { + new_streamFile = open_streamfile_by_filename(streamFile, bao->resource_name); + if (!new_streamFile) { VGM_LOG("UBI BAO: external stream '%s' not found\n", bao->resource_name); goto fail; } + stream_segments[1] = new_streamFile; - new_streamFile = open_clamp_streamfile(stream_segments[1], bao->stream_offset, (bao->stream_size - bao->prefetch_size)); - if (!new_streamFile) goto fail; - stream_segments[1] = new_streamFile; - temp_streamFile = NULL; + new_streamFile = open_clamp_streamfile(stream_segments[1], bao->stream_offset, (bao->stream_size - bao->prefetch_size)); + if (!new_streamFile) goto fail; + stream_segments[1] = new_streamFile; + temp_streamFile = NULL; - new_streamFile = open_multifile_streamfile(stream_segments, 2); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; - stream_segments[0] = NULL; - stream_segments[1] = NULL; + new_streamFile = open_multifile_streamfile(stream_segments, 2); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + stream_segments[0] = NULL; + stream_segments[1] = NULL; + } + else { + /* weird but happens, streamed chunk is empty in this case */ + temp_streamFile = new_streamFile; + stream_segments[0] = NULL; + } } else if (bao->is_external) { new_streamFile = open_streamfile_by_filename(streamFile, bao->resource_name); @@ -1721,6 +1735,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { case 0x001F0008: /* Rayman Raving Rabbids: TV Party (Wii)-package */ case 0x001F0010: /* Prince of Persia 2008 (PC/PS3/X360)-atomic-forge, Far Cry 2 (PS3)-atomic-dunia? */ case 0x001F0011: /* Naruto: The Broken Bond (X360)-package */ + case 0x0021000C: /* Splinter Cell: Conviction (E3 2009 Demo)(X360)-package */ case 0x0022000D: /* Just Dance (Wii)-package */ case 0x0022001B: /* Prince of Persia: The Forgotten Sands (Wii)-package */ config_bao_entry(bao, 0xA4, 0x28); /* PC/Wii: 0xA8 */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c index 396dc3827..cef0109ea 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c @@ -8,7 +8,7 @@ #define SB_MAX_CHAIN_COUNT 256 /* +150 exist in Tonic Trouble */ typedef enum { UBI_IMA, UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, RAW_AT3, FMT_XMA1, RAW_XMA1, FMT_OGG, FMT_CWAV, FMT_APM, FMT_MPDX, UBI_IMA_SCE } ubi_sb_codec; -typedef enum { UBI_PC, UBI_PS2, UBI_XBOX, UBI_GC, UBI_X360, UBI_PSP, UBI_PS3, UBI_WII, UBI_3DS } ubi_sb_platform; +typedef enum { UBI_PC, UBI_DC, UBI_PS2, UBI_XBOX, UBI_GC, UBI_X360, UBI_PSP, UBI_PS3, UBI_WII, UBI_3DS } ubi_sb_platform; typedef enum { UBI_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE, UBI_SILENCE } ubi_sb_type; typedef struct { @@ -18,14 +18,19 @@ typedef struct { size_t section2_entry_size; size_t section3_entry_size; size_t resource_name_size; + size_t blk_table_size; off_t audio_extra_offset; off_t audio_stream_size; off_t audio_stream_offset; off_t audio_stream_type; - off_t audio_group_id; - off_t audio_external_flag; + off_t audio_subblock_flag; + off_t audio_streamed_flag; + off_t audio_cd_streamed_flag; off_t audio_loop_flag; + off_t audio_loc_flag; + off_t audio_stereo_flag; + off_t audio_internal_flag; off_t audio_num_samples; off_t audio_num_samples2; off_t audio_sample_rate; @@ -33,10 +38,13 @@ typedef struct { off_t audio_stream_name; off_t audio_extra_name; off_t audio_xma_offset; - int audio_external_and; + off_t audio_pitch; + int audio_streamed_and; + int audio_cd_streamed_and; int audio_loop_and; - int audio_group_and; - int num_codec_flags; + int audio_subblock_and; + int audio_loc_and; + int audio_stereo_and; int audio_has_internal_names; size_t audio_interleave; int audio_fix_psx_samples; @@ -58,8 +66,11 @@ typedef struct { off_t layer_channels; off_t layer_stream_type; off_t layer_num_samples; + off_t layer_pitch; + off_t layer_loc_flag; + int layer_loc_and; size_t layer_entry_size; - size_t layer_hijack; + int layer_hijack; off_t silence_duration_int; off_t silence_duration_float; @@ -75,11 +86,12 @@ typedef struct { int is_padded_sectionX_offset; int is_padded_sounds_offset; int ignore_layer_error; - int default_codec_for_group0; + int default_codec_for_subblock0; } ubi_sb_config; typedef struct { ubi_sb_platform platform; + int is_ps2_old; int is_psp_old; int big_endian; int total_subsongs; @@ -92,12 +104,12 @@ typedef struct { /* map base header info */ off_t map_start; - off_t map_num; + uint32_t map_num; uint32_t map_type; uint32_t map_zero; off_t map_offset; - off_t map_size; + size_t map_size; char map_name[0x28]; uint32_t map_unknown; @@ -105,24 +117,32 @@ typedef struct { int is_bank; int is_map; int is_bnm; + int is_dat; + int is_ps2_bnm; + int is_blk; + STREAMFILE* sf_header; uint32_t version; /* 16b+16b major+minor version */ uint32_t version_empty; /* map sbX versions are empty */ /* events (often share header_id/type with some descriptors, * but may exist without headers or header exist without them) */ size_t section1_num; - size_t section1_offset; + off_t section1_offset; /* descriptors, audio header or other config types */ size_t section2_num; - size_t section2_offset; + off_t section2_offset; /* internal streams table, referenced by each header */ size_t section3_num; - size_t section3_offset; + off_t section3_offset; /* section with sounds in some map versions */ size_t section4_num; - size_t section4_offset; + off_t section4_offset; /* extra table, config for certain types (DSP coefs, external resources, layer headers, etc) */ size_t sectionX_size; - size_t sectionX_offset; + off_t sectionX_offset; + /* sound bank size */ + size_t bank_size; + /* BNM bank number */ + int bank_number; /* unknown, usually -1 but can be others (0/1/2/etc) */ int flag1; int flag2; @@ -138,7 +158,10 @@ typedef struct { off_t stream_offset; /* offset within the data section (internal) or absolute (external) to the audio */ size_t stream_size; /* size of the audio data */ uint32_t stream_type; /* rough codec value */ - uint32_t group_id; /* internal id to reference in section3 */ + uint32_t subblock_id; /* internal id to reference in section3 */ + uint8_t subbank_index; /* ID of the entry in DC bank */ + int is_localized; + int is_stereo; int loop_flag; /* stream loops (normally internal sfx, but also external music) */ int loop_start; /* usually 0 */ @@ -158,7 +181,9 @@ typedef struct { float duration; /* silence duration */ - int is_external; /* stream is in a external file */ + int is_streamed; /* sound is streamed from storage */ + int is_cd_streamed; /* found in PS2 BNM */ + int is_external; /* sound is in an external file */ char resource_name[0x28]; /* filename to the external stream, or internal stream info for some games */ char readable_name[255]; /* final subsong name */ @@ -166,25 +191,28 @@ typedef struct { int allowed_types[16]; } ubi_sb_header; -static int parse_bnm_header(ubi_sb_header * sb, STREAMFILE *streamFile); -static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset, int index); -static int parse_sb(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subsong); -static VGMSTREAM * init_vgmstream_ubi_sb_header(ubi_sb_header *sb, STREAMFILE* streamTest, STREAMFILE *streamFile); -static int config_sb_platform(ubi_sb_header * sb, STREAMFILE *streamFile); -static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile); +static int parse_bnm_header(ubi_sb_header* sb, STREAMFILE* sf); +static int parse_bnm_ps2_header(ubi_sb_header* sb, STREAMFILE* sf); +static int parse_dat_header(ubi_sb_header *sb, STREAMFILE *sf); +static int parse_header(ubi_sb_header* sb, STREAMFILE* sf, off_t offset, int index); +static int parse_sb(ubi_sb_header* sb, STREAMFILE* sf, int target_subsong); +static VGMSTREAM* init_vgmstream_ubi_sb_header(ubi_sb_header* sb, STREAMFILE* sf_index, STREAMFILE* sf); +static VGMSTREAM *init_vgmstream_ubi_sb_silence(ubi_sb_header *sb, STREAMFILE *sf_index, STREAMFILE *sf); +static int config_sb_platform(ubi_sb_header* sb, STREAMFILE* sf); +static int config_sb_version(ubi_sb_header* sb, STREAMFILE* sf); /* .SBx - banks from Ubisoft's DARE (Digital Audio Rendering Engine) engine games in ~2000-2008+ */ -VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { +VGMSTREAM* init_vgmstream_ubi_sb(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; - STREAMFILE *streamTest = NULL; + STREAMFILE* sf_index = NULL; int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; ubi_sb_header sb = {0}; - int target_subsong = streamFile->stream_index; + int target_subsong = sf->stream_index; /* checks (number represents the platform, see later) */ - if (!check_extensions(streamFile, "sb0,sb1,sb2,sb3,sb4,sb5,sb6,sb7")) + if (!check_extensions(sf, "sb0,sb1,sb2,sb3,sb4,sb5,sb6,sb7")) goto fail; /* .sbX (sound bank) is a small multisong format (loaded in memory?) that contains SFX data @@ -193,47 +221,47 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { /* PLATFORM DETECTION */ - if (!config_sb_platform(&sb, streamFile)) + if (!config_sb_platform(&sb, sf)) goto fail; read_32bit = sb.big_endian ? read_32bitBE : read_32bitLE; if (target_subsong <= 0) target_subsong = 1; /* use smaller header buffer for performance */ - streamTest = reopen_streamfile(streamFile, 0x100); - if (!streamTest) goto fail; + sf_index = reopen_streamfile(sf, 0x100); + if (!sf_index) goto fail; /* SB HEADER */ /* SBx layout: header, section1, section2, extra section, section3, data (all except header can be null) */ sb.is_bank = 1; - sb.version = read_32bit(0x00, streamFile); + sb.version = read_32bit(0x00, sf); - if (!config_sb_version(&sb, streamFile)) + if (!config_sb_version(&sb, sf)) goto fail; if (sb.version <= 0x0000000B) { - sb.section1_num = read_32bit(0x04, streamFile); - sb.section2_num = read_32bit(0x0c, streamFile); - sb.section3_num = read_32bit(0x14, streamFile); - sb.sectionX_size = read_32bit(0x1c, streamFile); + sb.section1_num = read_32bit(0x04, sf); + sb.section2_num = read_32bit(0x0c, sf); + sb.section3_num = read_32bit(0x14, sf); + sb.sectionX_size = read_32bit(0x1c, sf); sb.section1_offset = 0x20; } else if (sb.version <= 0x000A0000) { - sb.section1_num = read_32bit(0x04, streamFile); - sb.section2_num = read_32bit(0x08, streamFile); - sb.section3_num = read_32bit(0x0c, streamFile); - sb.sectionX_size = read_32bit(0x10, streamFile); - sb.flag1 = read_32bit(0x14, streamFile); + sb.section1_num = read_32bit(0x04, sf); + sb.section2_num = read_32bit(0x08, sf); + sb.section3_num = read_32bit(0x0c, sf); + sb.sectionX_size = read_32bit(0x10, sf); + sb.flag1 = read_32bit(0x14, sf); sb.section1_offset = 0x18; } else { - sb.section1_num = read_32bit(0x04, streamFile); - sb.section2_num = read_32bit(0x08, streamFile); - sb.section3_num = read_32bit(0x0c, streamFile); - sb.sectionX_size = read_32bit(0x10, streamFile); - sb.flag1 = read_32bit(0x14, streamFile); - sb.flag2 = read_32bit(0x18, streamFile); + sb.section1_num = read_32bit(0x04, sf); + sb.section2_num = read_32bit(0x08, sf); + sb.section3_num = read_32bit(0x0c, sf); + sb.sectionX_size = read_32bit(0x10, sf); + sb.flag1 = read_32bit(0x14, sf); + sb.flag2 = read_32bit(0x18, sf); sb.section1_offset = 0x1c; } @@ -253,31 +281,31 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { if (sb.cfg.is_padded_section3_offset) sb.section3_offset = align_size_to_block(sb.section3_offset, 0x10); - if (!parse_sb(&sb, streamTest, target_subsong)) + if (!parse_sb(&sb, sf_index, target_subsong)) goto fail; /* CREATE VGMSTREAM */ - vgmstream = init_vgmstream_ubi_sb_header(&sb, streamTest, streamFile); - close_streamfile(streamTest); + vgmstream = init_vgmstream_ubi_sb_header(&sb, sf_index, sf); + close_streamfile(sf_index); return vgmstream; fail: - close_streamfile(streamTest); + close_streamfile(sf_index); return NULL; } /* .SMx - maps (sets of custom SBx files) also from Ubisoft's sound engine games in ~2000-2008+ */ -VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { +VGMSTREAM* init_vgmstream_ubi_sm(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; - STREAMFILE *streamTest = NULL; + STREAMFILE* sf_index = NULL; int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; ubi_sb_header sb = {0}, target_sb = {0}; - int target_subsong = streamFile->stream_index; + int target_subsong = sf->stream_index; int i; /* checks (number represents platform, lmX are localized variations) */ - if (!check_extensions(streamFile, "sm0,sm1,sm2,sm3,sm4,sm5,sm6,sm7,lm0,lm1,lm2,lm3,lm4,lm5,lm6,lm7")) + if (!check_extensions(sf, "sm0,sm1,sm2,sm3,sm4,sm5,sm6,sm7,lm0,lm1,lm2,lm3,lm4,lm5,lm6,lm7")) goto fail; /* .smX (sound map) is a set of slightly different sbX files, compiled into one "map" file. @@ -286,25 +314,25 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { /* PLATFORM DETECTION */ - if (!config_sb_platform(&sb, streamFile)) + if (!config_sb_platform(&sb, sf)) goto fail; read_32bit = sb.big_endian ? read_32bitBE : read_32bitLE; if (target_subsong <= 0) target_subsong = 1; /* use smaller header buffer for performance */ - streamTest = reopen_streamfile(streamFile, 0x100); - if (!streamTest) goto fail; + sf_index = reopen_streamfile(sf, 0x100); + if (!sf_index) goto fail; /* SM BASE HEADER */ /* SMx layout: header with N map area offset/sizes + custom SBx with relative offsets */ sb.is_map = 1; - sb.version = read_32bit(0x00, streamFile); - sb.map_start = read_32bit(0x04, streamFile); - sb.map_num = read_32bit(0x08, streamFile); + sb.version = read_32bit(0x00, sf); + sb.map_start = read_32bit(0x04, sf); + sb.map_num = read_32bit(0x08, sf); - if (!config_sb_version(&sb, streamFile)) + if (!config_sb_version(&sb, sf)) goto fail; @@ -312,34 +340,34 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { off_t offset = sb.map_start + i * sb.cfg.map_entry_size; /* SUBMAP HEADER */ - sb.map_type = read_32bit(offset + 0x00, streamFile); /* usually 0/1=first, 0=rest */ - sb.map_zero = read_32bit(offset + 0x04, streamFile); - sb.map_offset = read_32bit(offset + 0x08, streamFile); - sb.map_size = read_32bit(offset + 0x0c, streamFile); /* includes sbX header, but not internal streams */ - read_string(sb.map_name, sizeof(sb.map_name), offset + 0x10, streamFile); /* null-terminated and may contain garbage after null */ + sb.map_type = read_32bit(offset + 0x00, sf); /* usually 0/1=first, 0=rest */ + sb.map_zero = read_32bit(offset + 0x04, sf); + sb.map_offset = read_32bit(offset + 0x08, sf); + sb.map_size = read_32bit(offset + 0x0c, sf); /* includes sbX header, but not internal streams */ + read_string(sb.map_name, sizeof(sb.map_name), offset + 0x10, sf); /* null-terminated and may contain garbage after null */ if (sb.cfg.map_version >= 3) - sb.map_unknown = read_32bit(offset + 0x30, streamFile); /* uncommon, id/config? longer name? mem garbage? */ + sb.map_unknown = read_32bit(offset + 0x30, sf); /* uncommon, id/config? longer name? mem garbage? */ /* SB HEADER */ /* SBx layout: base header, section1, section2, section4, extra section, section3, data (all except header can be null?) */ - sb.version_empty = read_32bit(sb.map_offset + 0x00, streamFile); /* sbX in maps don't set version */ - sb.section1_offset = read_32bit(sb.map_offset + 0x04, streamFile) + sb.map_offset; - sb.section1_num = read_32bit(sb.map_offset + 0x08, streamFile); - sb.section2_offset = read_32bit(sb.map_offset + 0x0c, streamFile) + sb.map_offset; - sb.section2_num = read_32bit(sb.map_offset + 0x10, streamFile); + sb.version_empty = read_32bit(sb.map_offset + 0x00, sf); /* sbX in maps don't set version */ + sb.section1_offset = read_32bit(sb.map_offset + 0x04, sf) + sb.map_offset; + sb.section1_num = read_32bit(sb.map_offset + 0x08, sf); + sb.section2_offset = read_32bit(sb.map_offset + 0x0c, sf) + sb.map_offset; + sb.section2_num = read_32bit(sb.map_offset + 0x10, sf); if (sb.cfg.map_version < 3) { - sb.section3_offset = read_32bit(sb.map_offset + 0x14, streamFile) + sb.map_offset; - sb.section3_num = read_32bit(sb.map_offset + 0x18, streamFile); - sb.sectionX_offset = read_32bit(sb.map_offset + 0x1c, streamFile) + sb.map_offset; - sb.sectionX_size = read_32bit(sb.map_offset + 0x20, streamFile); + sb.section3_offset = read_32bit(sb.map_offset + 0x14, sf) + sb.map_offset; + sb.section3_num = read_32bit(sb.map_offset + 0x18, sf); + sb.sectionX_offset = read_32bit(sb.map_offset + 0x1c, sf) + sb.map_offset; + sb.sectionX_size = read_32bit(sb.map_offset + 0x20, sf); } else { - sb.section4_offset = read_32bit(sb.map_offset + 0x14, streamFile); - sb.section4_num = read_32bit(sb.map_offset + 0x18, streamFile); - sb.section3_offset = read_32bit(sb.map_offset + 0x1c, streamFile) + sb.map_offset; - sb.section3_num = read_32bit(sb.map_offset + 0x20, streamFile); - sb.sectionX_offset = read_32bit(sb.map_offset + 0x24, streamFile) + sb.map_offset; - sb.sectionX_size = read_32bit(sb.map_offset + 0x28, streamFile); + sb.section4_offset = read_32bit(sb.map_offset + 0x14, sf); + sb.section4_num = read_32bit(sb.map_offset + 0x18, sf); + sb.section3_offset = read_32bit(sb.map_offset + 0x1c, sf) + sb.map_offset; + sb.section3_num = read_32bit(sb.map_offset + 0x20, sf); + sb.sectionX_offset = read_32bit(sb.map_offset + 0x24, sf) + sb.map_offset; + sb.sectionX_size = read_32bit(sb.map_offset + 0x28, sf); /* latest map format has another section with sounds after section 2 */ sb.section2_num += sb.section4_num; /* let's just merge it with section 2 */ @@ -351,7 +379,7 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { //;VGM_ASSERT(sb.map_unknown != 0, "UBI SM: unknown map_unknown at %x\n", (uint32_t)offset); VGM_ASSERT(sb.version_empty != 0, "UBI SM: unknown version_empty at %x\n", (uint32_t)offset); - if (!parse_sb(&sb, streamTest, target_subsong)) + if (!parse_sb(&sb, sf_index, target_subsong)) goto fail; /* snapshot of current sb if subsong was found @@ -365,77 +393,77 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) { target_sb.total_subsongs = sb.total_subsongs; /* CREATE VGMSTREAM */ - vgmstream = init_vgmstream_ubi_sb_header(&target_sb, streamTest, streamFile); - close_streamfile(streamTest); + vgmstream = init_vgmstream_ubi_sb_header(&target_sb, sf_index, sf); + close_streamfile(sf_index); return vgmstream; fail: - close_streamfile(streamTest); + close_streamfile(sf_index); return NULL; } /* .BNM - proto-sbX with map style format [Rayman 2 (PC), Donald Duck: Goin' Quackers (PC), Tonic Trouble (PC)] */ -VGMSTREAM * init_vgmstream_ubi_bnm(STREAMFILE *streamFile) { +VGMSTREAM* init_vgmstream_ubi_bnm(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; - STREAMFILE *streamTest = NULL; + STREAMFILE* sf_index = NULL; ubi_sb_header sb = {0}; - int target_subsong = streamFile->stream_index; + int target_subsong = sf->stream_index; if (target_subsong <= 0) target_subsong = 1; /* checks */ - if (!check_extensions(streamFile, "bnm")) + if (!check_extensions(sf, "bnm")) goto fail; /* v0, header is somewhat like a map-style bank (offsets + sizes) but sectionX/3 fields are - * fixed/reserved (unused?). Header entry sizes and config works the same, and type numbers are - * slightly different, but otherwise pretty much the same engine (not named DARE yet). Curiously - * it may stream RIFF .wav (stream_offset pointing to "data"), and also .raw (PCM) or .apm IMA. */ + * fixed/reserved. Header entry sizes and config works the same, and type numbers are slightly + * different, but otherwise pretty much the same engine (not named DARE yet). Curiously, it may + * stream RIFF .wav (stream_offset pointing to "data"), and also .raw (PCM) or .apm IMA. */ - /* use smaller header buffer for performance */ - streamTest = reopen_streamfile(streamFile, 0x100); - if (!streamTest) goto fail; - - if (!parse_bnm_header(&sb, streamTest)) + if (!parse_bnm_header(&sb, sf)) goto fail; - if (!parse_sb(&sb, streamTest, target_subsong)) + /* use smaller header buffer for performance */ + sf_index = reopen_streamfile(sf, 0x100); + if (!sf_index) goto fail; + + if (!parse_sb(&sb, sf_index, target_subsong)) goto fail; /* CREATE VGMSTREAM */ - vgmstream = init_vgmstream_ubi_sb_header(&sb, streamTest, streamFile); - close_streamfile(streamTest); + vgmstream = init_vgmstream_ubi_sb_header(&sb, sf_index, sf); + close_streamfile(sf_index); return vgmstream; fail: - close_streamfile(streamTest); + close_streamfile(sf_index); return NULL; } -static int parse_bnm_header(ubi_sb_header * sb, STREAMFILE *streamFile) { +static int parse_bnm_header(ubi_sb_header* sb, STREAMFILE* sf) { int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; /* PLATFORM DETECTION */ sb->platform = UBI_PC; + sb->big_endian = 0; read_32bit = sb->big_endian ? read_32bitBE : read_32bitLE; /* SB HEADER */ /* SBx layout: header, section1, section2, extra section, section3, data (all except header can be null) */ sb->is_bnm = 1; - sb->version = read_32bit(0x00, streamFile); - sb->section1_offset = read_32bit(0x04, streamFile); - sb->section1_num = read_32bit(0x08, streamFile); - sb->section2_offset = read_32bit(0x0c, streamFile); - sb->section2_num = read_32bit(0x10, streamFile); - /* next are data start offset x3 + data size offset x3 */ - sb->section3_offset = read_32bit(0x14, streamFile); - sb->section3_num = 0; - - if (!config_sb_version(sb, streamFile)) + sb->version = read_32bit(0x00, sf); + if (!config_sb_version(sb, sf)) goto fail; + sb->section1_offset = read_32bit(0x04, sf); + sb->section1_num = read_32bit(0x08, sf); + sb->section2_offset = read_32bit(0x0c, sf); + sb->section2_num = read_32bit(0x10, sf); + sb->section3_offset = read_32bit(0x14, sf); + sb->section3_num = 0; + sb->sectionX_offset = sb->section2_offset + sb->section2_num * sb->cfg.section2_entry_size; sb->sectionX_size = sb->section3_offset - sb->sectionX_offset; @@ -444,37 +472,502 @@ fail: return 0; } -static int is_bnm_other_bank(STREAMFILE *streamFile, int bank_number) { +static int bnm_parse_offsets(ubi_sb_header *sb, STREAMFILE *sf) { + int32_t(*read_32bit)(off_t, STREAMFILE *) = sb->big_endian ? read_32bitBE : read_32bitLE; + uint32_t block_offset; + + if (sb->is_external) + return 1; + + /* sounds are split into subblocks based on resource type and codec, the order is hardcoded */ + if (sb->version == 0x00000000 || sb->version == 0x00000200) { + /* 0x14: MPDX, 0x18: MIDI, 0x1c: PCM, 0x20: APM, 0x24: streamed, 0x28: EOF */ + switch (sb->stream_type) { + case 0x01: + block_offset = read_32bit(0x1c, sf); + break; + case 0x02: + block_offset = read_32bit(0x14, sf); + break; + case 0x04: + block_offset = read_32bit(0x20, sf); + break; + default: + goto fail; + } + } else if (sb->version == 0x00060409) { + /* The Jungle Book is stripped down compared to other versions */ + /* 0x14: Ubi ADPCM, 0x18: PCM, 0x1c: streamed */ + switch (sb->stream_type) { + case 0x01: + block_offset = read_32bit(0x18, sf); + break; + case 0x06: + block_offset = read_32bit(0x14, sf); + break; + default: + goto fail; + } + } else { + VGM_LOG("UBI BNM: Unknown subblock offsets for version %08x", sb->version); + goto fail; + } + + sb->stream_offset += block_offset; + + return 1; +fail: + return 0; +} + +static int parse_ubi_bank_header(ubi_sb_header *sb, ubi_sb_header *sb_other, STREAMFILE *sf) { + if (sb->is_bnm) { + return parse_bnm_header(sb_other, sf); + } else if (sb->is_dat) { + return parse_dat_header(sb_other, sf); + } else if (sb->is_ps2_bnm) { + return parse_bnm_ps2_header(sb_other, sf); + } + + return 0; +} + +static void get_ubi_bank_name(ubi_sb_header *sb, STREAMFILE *sf, int bank_number, char *bank_name) { + if (sb->is_bnm) { + sprintf(bank_name, "Bnk_%d.bnm", bank_number); + } else if (sb->is_dat) { + sprintf(bank_name, "BNK_%d.DAT", bank_number); + } else if (sb->is_ps2_bnm) { + sprintf(bank_name, "BNK_%d.BNM", bank_number); + } else { + strcpy(bank_name, "ERROR"); + } +} + +static int is_other_bank(ubi_sb_header *sb, STREAMFILE *sf, int bank_number) { char current_name[PATH_LIMIT]; char bank_name[255]; - get_streamfile_filename(streamFile, current_name, PATH_LIMIT); - sprintf(bank_name, "Bnk_%i.bnm", bank_number); + get_streamfile_filename(sf, current_name, PATH_LIMIT); + get_ubi_bank_name(sb, sf, bank_number, bank_name); return strcmp(current_name, bank_name) != 0; } -#if 0 -/* .BLK - maps in separate .blk chunks [Donald Duck: Goin' Quackers (PS2), The Jungle Book Rhythm N'Groove (PS2)] */ -VGMSTREAM * init_vgmstream_ubi_blk(STREAMFILE *streamFile) { +/* .DAT - very similar to BNM, used on Dreamcast */ +VGMSTREAM *init_vgmstream_ubi_dat(STREAMFILE *sf) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *sf_index = NULL; + ubi_sb_header sb = { 0 }; + int target_subsong = sf->stream_index; - /* Somewhat equivalent to a v0x00000003 map: - * - HEADER.BLK: base map header (slightly different?) + submaps headers - * - EVT.BLK: section1 from all submaps - * - RES.BLK: section2 + sectionX from all submaps - * - MAPS.BLK, MAPLANG.BLK: section3 variation? - * - STREAMED.BLK, STRLANG.BLK: audio data - * - * Parsing may be be simplified with multifile_streamfiles? - */ + if (target_subsong <= 0) target_subsong = 1; + + /* checks */ + if (!check_extensions(sf, "dat")) + goto fail; + + if (!parse_dat_header(&sb, sf)) + goto fail; + + /* use smaller header buffer for performance */ + sf_index = reopen_streamfile(sf, 0x100); + if (!sf_index) goto fail; + + if (!parse_sb(&sb, sf_index, target_subsong)) + goto fail; + + /* CREATE VGMSTREAM */ + vgmstream = init_vgmstream_ubi_sb_header(&sb, sf_index, sf); + close_streamfile(sf_index); + return vgmstream; + +fail: + close_streamfile(sf_index); return NULL; } -#endif + +static int parse_dat_header(ubi_sb_header *sb, STREAMFILE *sf) { + int32_t(*read_32bit)(off_t, STREAMFILE *) = NULL; + + /* only used on DC */ + sb->platform = UBI_DC; + sb->big_endian = 0; + read_32bit = sb->big_endian ? read_32bitBE : read_32bitLE; + + sb->is_dat = 1; + sb->version = read_32bit(0x00, sf); + if (sb->version != 0x00000000) + goto fail; + + if (!config_sb_version(sb, sf)) + goto fail; + + sb->section1_offset = read_32bit(0x04, sf); + sb->section1_num = read_32bit(0x08, sf); + sb->section2_offset = read_32bit(0x0c, sf); + sb->section2_num = read_32bit(0x10, sf); + sb->bank_size = read_32bit(0x14, sf); + + if (sb->section1_offset != 0x18) + goto fail; + + if (sb->section2_offset != sb->section1_offset + sb->section1_num * sb->cfg.section1_entry_size) + goto fail; + + if (sb->bank_size != get_streamfile_size(sf)) + goto fail; + + sb->sectionX_offset = sb->section2_offset + sb->section2_num * sb->cfg.section2_entry_size; + sb->sectionX_size = sb->bank_size - sb->sectionX_offset; + + return 1; +fail: + return 0; +} + +static VGMSTREAM *init_vgmstream_ubi_dat_main(ubi_sb_header *sb, STREAMFILE *sf_index, STREAMFILE *sf) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *sf_data = NULL; + + if (sb->is_external) { + if (strcmp(sb->resource_name, "silence.wav") == 0) { + /* some Rayman 2 banks reference non-existent silence.wav, looks like some kind of hack? */ + sb->duration = (float)(sb->stream_size / sb->channels / 2) / (float)sb->sample_rate; + return init_vgmstream_ubi_sb_silence(sb, sf_index, sf); + } + + sf_data = open_streamfile_by_filename(sf, sb->resource_name); + if (!sf_data) { + VGM_LOG("UBI DAT: no matching KAT found\n"); + goto fail; + } + } + + /* DAT banks don't work with raw audio data, they open full external files and rely almost entirely + * on their metadata, that's why we're handling this here, separately from other types */ + switch (sb->stream_type) { + case 0x01: + { + if (!sb->is_external) { /* Dreamcast bank */ + if (sb->version == 0x00000000) { + uint32_t entry_offset, start_offset, num_samples, codec; + uint8_t buf[4]; + + sf_data = open_streamfile_by_ext(sf, "osb"); + if (!sf_data) { + VGM_LOG("UBI DAT: no matching OSB found\n"); + goto fail; + } + + /* FIXME: hacky handling of OSB bank, need to eventually write a full parser once + * the format is fully cracked */ + entry_offset = read_32bitLE(0x10 + sb->subbank_index * 0x04, sf_data); + + /* stores values in a weird zig-zag pattern */ + if (read_streamfile(buf, entry_offset + 0x04, 4, sf_data) != 4) goto fail; + start_offset = (buf[0] << 16) | (buf[2]) | (buf[3] << 8); + if (read_streamfile(buf, entry_offset + 0x08, 4, sf_data) != 4) goto fail; + num_samples = (buf[0] << 16) | (buf[1] << 24) | (buf[2]) | (buf[3] << 8); + num_samples /= sb->channels; + codec = read_8bit(entry_offset + 0x05, sf_data); + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(sb->channels, sb->loop_flag); + if (!vgmstream) goto fail; + + if (codec == 0) { + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + vgmstream->stream_size = num_samples * sb->channels * 2; + } else { + vgmstream->coding_type = coding_AICA_int; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x01; + vgmstream->stream_size = num_samples * sb->channels / 2; + } + + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = sb->loop_start; + vgmstream->loop_end_sample = vgmstream->num_samples; + + if (!vgmstream_open_stream(vgmstream, sf_data, start_offset)) + goto fail; + } else if (sb->version == 0x00000200) { + sf_data = open_streamfile_by_ext(sf, "kat"); + if (!sf_data) { + VGM_LOG("UBI DAT: no matching KAT found\n"); + goto fail; + } + + /* KAT defines its own loop points */ + sf_data->stream_index = sb->subbank_index + 1; + vgmstream = init_vgmstream_kat(sf_data); + if (!vgmstream) goto fail; + } else { + goto fail; + } + } else { /* raw PCM */ + uint32_t data_size; + + vgmstream = allocate_vgmstream(sb->channels, sb->loop_flag); + if (!vgmstream) goto fail; + + data_size = get_streamfile_size(sf_data) - sb->stream_offset; + + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + vgmstream->num_samples = pcm_bytes_to_samples(data_size, sb->channels, 16); + vgmstream->loop_start_sample = sb->loop_start; + vgmstream->loop_end_sample = vgmstream->num_samples; + + if (!vgmstream_open_stream(vgmstream, sf_data, sb->stream_offset)) + goto fail; + } + break; + } + case 0x04:{ /* standard WAV */ + if (!sb->is_external) { + VGM_LOG("Ubi DAT: Found RAM stream_type 0x04\n"); + goto fail; + } + + vgmstream = init_vgmstream_riff(sf_data); + if (!vgmstream) goto fail; + break; + } + default: + VGM_LOG("UBI DAT: Unkown stream_type %d\n", sb->stream_type); + goto fail; + } + + vgmstream->meta_type = meta_UBI_SB; + vgmstream->num_streams = sb->total_subsongs; + vgmstream->sample_rate = sb->sample_rate; + + close_streamfile(sf_data); + return vgmstream; + +fail: + close_vgmstream(vgmstream); + close_streamfile(sf_data); + return NULL; +} + +/* .BNM - used in the earliest PS2 games */ +VGMSTREAM *init_vgmstream_ubi_bnm_ps2(STREAMFILE *sf) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *sf_index = NULL; + ubi_sb_header sb = { 0 }; + int target_subsong = sf->stream_index; + + if (target_subsong <= 0) target_subsong = 1; + + /* checks */ + if (!check_extensions(sf, "bnm")) + goto fail; + + if (!parse_bnm_ps2_header(&sb, sf)) + goto fail; + + /* use smaller header buffer for performance */ + sf_index = reopen_streamfile(sf, 0x100); + if (!sf_index) goto fail; + + if (!parse_sb(&sb, sf_index, target_subsong)) + goto fail; + + /* CREATE VGMSTREAM */ + vgmstream = init_vgmstream_ubi_sb_header(&sb, sf_index, sf); + close_streamfile(sf_index); + return vgmstream; + +fail: + close_streamfile(sf_index); + return NULL; +} + +static int parse_bnm_ps2_header(ubi_sb_header* sb, STREAMFILE* sf) { + int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; + + sb->platform = UBI_PS2; + sb->big_endian = 0; + read_32bit = sb->big_endian ? read_32bitBE : read_32bitLE; + + /* SB HEADER */ + /* SBx layout: header, section1, section2, extra section, section3, data (all except header can be null) */ + sb->is_ps2_bnm = 1; + sb->version = read_32bit(0x00, sf); + if (sb->version != 0x32787370) /* "psx2" */ + goto fail; + + if (!config_sb_version(sb, sf)) + goto fail; + + sb->bank_number = read_32bit(0x04, sf); + sb->section1_offset = read_32bit(0x08, sf); + sb->section1_num = read_32bit(0x0c, sf); + sb->section2_offset = read_32bit(0x10, sf); + sb->section2_num = read_32bit(0x14, sf); + sb->sectionX_offset = read_32bit(0x18, sf); + sb->bank_size = read_32bit(0x1c, sf); + sb->flag1 = read_32bit(0x20, sf); + + sb->sectionX_size = sb->bank_size - sb->sectionX_offset; + + return 1; +fail: + return 0; +} + +/* .BLK - maps in separate .blk chunks [Donald Duck: Goin' Quackers (PS2), The Jungle Book: Rhythm N'Groove (PS2)] */ +VGMSTREAM* init_vgmstream_ubi_blk(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE* sf_res = NULL, *sf_index = NULL; + ubi_sb_header sb = { 0 }; + int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; + int target_subsong = sf->stream_index; + + /* Somewhat equivalent to a v0x00000003 map: + * - HEADER.BLK: base map header + submaps headers + * - EVT.BLK: section1 + * - RES.BLK: section2 + sectionX + * - MAP.BLK, MAPLANG.BLK: section3's for each map + * - STREAMED.BLK, STRLANG.BLK: streamed sounds + * + * The format is different from SMx in that there's a single sec1 and sec2 + * shared by all maps so we can't determine which sounds belong to which map. + * Meanwhile, RAM sounds are stored in MAP.BLK/MAPLANG.BLK, which are split into blocks, + * one per map, which contain all RAM sounds that should be loaded for a given map. + * 0x00: version + * 0x04: number of maps + * 0x08: number of events (EVT.BLK) + * 0x0c: number of resources (RES.BLK) + * 0x10: flags? + * 0x14: size of extra section in RES.BLK + * for each map: + * 0x00: total size of common RAM sounds + * 0x04: total size of localized RAM sounds + * 0x08: offset of common sound table in MAP.BLK + * 0x0c: offset of localized sound table in MAPLANG.BLK + * 0x10: map name (0x20 bytes) + */ + + /* checks */ + if (!check_extensions(sf, "blk")) + goto fail; + + /* only known to be used on PS2 */ + sb.platform = UBI_PS2; + sb.big_endian = 0; + read_32bit = sb.big_endian ? read_32bitBE : read_32bitLE; + + /* must open HEADER.BLK */ + sb.is_blk = 1; + sb.version = read_32bit(0x00, sf) & 0x7FFFFFFF; + if (read_32bit(0x00, sf) & 0x80000000) { + sb.cfg.blk_table_size = 0x2000; + } else { + sb.cfg.blk_table_size = 0x1800; + } + if (sb.version != 0x00000003) + goto fail; + + if (!config_sb_version(&sb, sf)) + goto fail; + + sb.sf_header = sf; + sb.map_num = read_32bit(0x04, sf); + sb.section1_num = read_32bit(0x08, sf); + sb.section1_offset = 0; + sb.section2_num = read_32bit(0x0c, sf); + sb.section2_offset = 0; + sb.sectionX_offset = sb.section2_num * sb.cfg.section2_entry_size; + sb.sectionX_size = read_32bit(0x14, sf); + + /* ugh... */ + sf_res = open_streamfile_by_filename(sf, "RES.BLK"); + sf_index = reopen_streamfile(sf_res, 0x100); + if (target_subsong == 0) target_subsong = 1; + + if (!parse_sb(&sb, sf_index, target_subsong)) + goto fail; + + /* CREATE VGMSTREAM */ + vgmstream = init_vgmstream_ubi_sb_header(&sb, sf_index, sf_res); + close_streamfile(sf_res); + close_streamfile(sf_index); + return vgmstream; + +fail: + close_streamfile(sf_res); + close_streamfile(sf_index); + return NULL; +} + +static int blk_parse_offsets(ubi_sb_header* sb) { + uint32_t i; + int32_t(*read_32bit)(off_t, STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + + /* correct offsets */ + if (sb->is_streamed) { + /* offsets for streamed sounds are stored in sectors */ + sb->stream_offset *= 0x800; + } else { + STREAMFILE* sf_snd = NULL; + + /* find the first map block which has this sound */ + sf_snd = open_streamfile_by_filename(sb->sf_header, sb->resource_name); + if (!sf_snd) goto fail; + for (i = 0; i < sb->map_num; i++) { + uint32_t entry_offset, cmn_table_offset, loc_table_offset, table_offset; + entry_offset = 0x18 + i * sb->cfg.map_entry_size; + cmn_table_offset = read_32bit(entry_offset + 0x08, sb->sf_header); + loc_table_offset = read_32bit(entry_offset + 0x0c, sb->sf_header); + table_offset = sb->is_localized ? loc_table_offset : cmn_table_offset; + + sb->stream_offset = read_32bit(table_offset + sb->header_index * 0x04, sf_snd); + if (sb->stream_offset != 0xFFFFFFFF) { + sb->stream_offset += table_offset + sb->cfg.blk_table_size; + //sb->stream_size -= 0x04; + break; + } + } + close_streamfile(sf_snd); + + if (sb->stream_offset == 0xFFFFFFFF) { + VGM_LOG("UBI BLK: No map block contains resource %08x (%d)\n", sb->header_id, sb->header_index); + goto fail; + } + } + + return 1; +fail: + return 0; +} + +static void blk_get_resource_name(ubi_sb_header* sb) { + if (sb->is_streamed) { + if (sb->is_localized) { + strcpy(sb->resource_name, "STRLANG.BLK"); + } else { + strcpy(sb->resource_name, "../STREAMED.BLK"); + } + } else { + if (sb->is_localized) { + strcpy(sb->resource_name, "MAPLANG.BLK"); + } else { + strcpy(sb->resource_name, "../MAP.BLK"); + } + } +} /* ************************************************************************* */ -static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *streamHead, STREAMFILE *streamData, off_t start_offset) { - VGMSTREAM * vgmstream = NULL; +static VGMSTREAM* init_vgmstream_ubi_sb_base(ubi_sb_header* sb, STREAMFILE* sf_head, STREAMFILE* sf_data, off_t start_offset) { + VGMSTREAM* vgmstream = NULL; /* build the VGMSTREAM */ @@ -499,7 +992,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str case UBI_IMA_SCE: vgmstream->coding_type = coding_UBI_IMA; vgmstream->layout_type = layout_blocked_ubi_sce; - vgmstream->full_block_size = read_32bitLE(0x18, streamData); + vgmstream->full_block_size = read_32bitLE(0x18, sf_data); /* this "codec" is an ugly hack of IMA w/ Ubi ADPCM's frame format, surely to * shoehorn a simpler codec into the existing code when porting the game */ @@ -514,13 +1007,13 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str * - possibly others */ /* skip extra header (some kind of id?) found in Myst IV */ - if (read_32bitBE(start_offset + 0x00, streamData) != 0x08000000 && - read_32bitBE(start_offset + 0x08, streamData) == 0x08000000) { + if (read_32bitBE(start_offset + 0x00, sf_data) != 0x08000000 && + read_32bitBE(start_offset + 0x08, sf_data) == 0x08000000) { start_offset += 0x08; sb->stream_size -= 0x08; } - vgmstream->codec_data = init_ubi_adpcm(streamData, start_offset, vgmstream->channels); + vgmstream->codec_data = init_ubi_adpcm(sf_data, start_offset, vgmstream->channels); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_UBI_ADPCM; vgmstream->layout_type = layout_none; @@ -541,9 +1034,15 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str case RAW_PSX: vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = (sb->cfg.audio_interleave) ? - sb->cfg.audio_interleave : - sb->stream_size / sb->channels; + if (sb->is_ps2_bnm) { + vgmstream->interleave_block_size = (sb->is_cd_streamed) ? + sb->cfg.audio_interleave : + sb->stream_size / sb->channels; + } else { + vgmstream->interleave_block_size = (sb->cfg.audio_interleave) ? + sb->cfg.audio_interleave : + sb->stream_size / sb->channels; + } if (vgmstream->num_samples == 0) { /* early PS2 games may not set it for internal streams */ vgmstream->num_samples = ps_bytes_to_samples(sb->stream_size, sb->channels); @@ -571,13 +1070,13 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str vgmstream->interleave_block_size = align_size_to_block(sb->stream_size / sb->channels, 0x08); /* frame-aligned */ /* mini DSP header (first 0x10 seem to contain DSP header fields like nibbles and format) */ - dsp_read_coefs_be(vgmstream, streamHead, sb->extra_offset + 0x10, 0x40); - dsp_read_hist_be (vgmstream, streamHead, sb->extra_offset + 0x34, 0x40); /* after gain/initial ps */ + dsp_read_coefs_be(vgmstream, sf_head, sb->extra_offset + 0x10, 0x40); + dsp_read_hist_be (vgmstream, sf_head, sb->extra_offset + 0x34, 0x40); /* after gain/initial ps */ break; case FMT_VAG: /* skip VAG header (some sb4 use VAG and others raw PSX) */ - if (read_32bitBE(start_offset, streamData) == 0x56414770) { /* "VAGp" */ + if (read_32bitBE(start_offset, sf_data) == 0x56414770) { /* "VAGp" */ start_offset += 0x30; sb->stream_size -= 0x30; } @@ -590,13 +1089,13 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str #ifdef VGM_USE_FFMPEG case FMT_AT3: { /* skip weird value (3 or 4) in Brothers in Arms: D-Day (PSP) */ - if (read_32bitBE(start_offset+0x04,streamData) == 0x52494646) { - VGM_LOG("UBI SB: skipping unknown value 0x%x before RIFF\n", read_32bitBE(start_offset+0x00,streamData)); + if (read_32bitBE(start_offset+0x04,sf_data) == 0x52494646) { + VGM_LOG("UBI SB: skipping unknown value 0x%x before RIFF\n", read_32bitBE(start_offset+0x00,sf_data)); start_offset += 0x04; sb->stream_size -= 0x04; } - vgmstream->codec_data = init_ffmpeg_atrac3_riff(streamData, start_offset, NULL); + vgmstream->codec_data = init_ffmpeg_atrac3_riff(sf_data, start_offset, NULL); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; @@ -609,14 +1108,16 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str block_align = 0x98 * sb->channels; encoder_delay = 1024 + 69*2; /* approximate */ - vgmstream->codec_data = init_ffmpeg_atrac3_raw(streamData, start_offset,sb->stream_size, sb->num_samples,sb->channels,sb->sample_rate, block_align, encoder_delay); + vgmstream->codec_data = init_ffmpeg_atrac3_raw(sf_data, start_offset,sb->stream_size, sb->num_samples,sb->channels,sb->sample_rate, block_align, encoder_delay); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; break; } - //todo: some XMA1 decode a bit strangely at certain positions (FFmpeg bug?) + //TODO: Ubi XMA1 (raw or fmt) is a bit strange, FFmpeg decodes some frames slightly wrong + // XMA1 normally has a frame counter in the first nibble but Ubi's is always set to 0. + // Probably a beta/custom encoder that creates some buggy frames, that a real X360 handles ok, but trips FFmpeg case FMT_XMA1: { ffmpeg_codec_data *ffmpeg_data; uint8_t buf[0x100]; @@ -629,10 +1130,10 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str /* formatted XMA sounds have a strange custom header */ header_offset = start_offset; /* XMA fmt chunk at the start */ - flag = read_8bit(header_offset + 0x20, streamData); - sec2_num = read_32bitBE(header_offset + 0x24, streamData); /* number of XMA frames */ - sec1_num = read_32bitBE(header_offset + 0x28, streamData); - sec3_num = read_32bitBE(header_offset + 0x2c, streamData); + flag = read_8bit(header_offset + 0x20, sf_data); + sec2_num = read_32bitBE(header_offset + 0x24, sf_data); /* number of XMA frames */ + sec1_num = read_32bitBE(header_offset + 0x28, sf_data); + sec3_num = read_32bitBE(header_offset + 0x2c, sf_data); bits_per_frame = 4; if (flag == 0x02 || flag == 0x04) @@ -647,15 +1148,15 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str start_offset += header_size; data_size = sec2_num * 0x800; - bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, data_size, streamData, 1); + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, data_size, sf_data, 1); - ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, data_size); + ffmpeg_data = init_ffmpeg_header_offset(sf_data, buf, bytes, start_offset, data_size); if (!ffmpeg_data) goto fail; vgmstream->codec_data = ffmpeg_data; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - xma_fix_raw_samples_ch(vgmstream, streamData, start_offset, data_size, sb->channels, 0, 0); + xma_fix_raw_samples_ch(vgmstream, sf_data, start_offset, data_size, sb->channels, 0, 0); break; } @@ -665,7 +1166,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str size_t bytes, chunk_size; off_t header_offset; - VGM_ASSERT(sb->is_external, "Ubi SB: Raw XMA used for external sound\n"); + VGM_ASSERT(sb->is_streamed, "Ubi SB: Raw XMA used for streamed sound\n"); /* get XMA header from extra section */ chunk_size = 0x20; @@ -673,21 +1174,21 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str if (header_offset == 0) header_offset = sb->extra_offset; - bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, sb->stream_size, streamHead, 1); + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, sb->stream_size, sf_head, 1); - ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb->stream_size); + ffmpeg_data = init_ffmpeg_header_offset(sf_data, buf, bytes, start_offset, sb->stream_size); if (!ffmpeg_data) goto fail; vgmstream->codec_data = ffmpeg_data; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; - xma_fix_raw_samples_ch(vgmstream, streamData, start_offset, sb->stream_size, sb->channels, 0, 0); + xma_fix_raw_samples_ch(vgmstream, sf_data, start_offset, sb->stream_size, sb->channels, 0, 0); break; } #endif #ifdef VGM_USE_VORBIS case FMT_OGG: { - vgmstream->codec_data = init_ogg_vorbis(streamData, start_offset, sb->stream_size, NULL); + vgmstream->codec_data = init_ogg_vorbis(sf_data, start_offset, sb->stream_size, NULL); if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_OGG_VORBIS; vgmstream->layout_type = layout_none; @@ -700,7 +1201,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x08; - dsp_read_coefs_le(vgmstream,streamData,start_offset + 0x7c, 0x40); + dsp_read_coefs_le(vgmstream,sf_data,start_offset + 0x7c, 0x40); start_offset += 0xe0; /* skip CWAV header */ break; @@ -736,8 +1237,8 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str { int i; for (i = 0; i < sb->channels; i++) { - vgmstream->ch[i].adpcm_history1_32 = read_32bitLE(start_offset + 0x2c + 0x0c*(sb->channels - 1 - i) + 0x00, streamData); - vgmstream->ch[i].adpcm_step_index = read_32bitLE(start_offset + 0x2c + 0x0c*(sb->channels - 1 - i) + 0x04, streamData); + vgmstream->ch[i].adpcm_history1_32 = read_32bitLE(start_offset + 0x2c + 0x0c*(sb->channels - 1 - i) + 0x00, sf_data); + vgmstream->ch[i].adpcm_step_index = read_32bitLE(start_offset + 0x2c + 0x0c*(sb->channels - 1 - i) + 0x04, sf_data); } } //todo supposedly APM IMA removes lower 3b after assigning step, but wave looks a bit off (Rayman 2 only?): @@ -765,65 +1266,74 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str goto fail; } - /* open the actual for decoding (streamData can be an internal or external stream) */ - if ( !vgmstream_open_stream(vgmstream, streamData, start_offset) ) + /* open the actual for decoding (sf_data can be an internal or external stream) */ + if ( !vgmstream_open_stream(vgmstream, sf_data, start_offset) ) goto fail; return vgmstream; fail: + VGM_LOG("UBI SB: init vgmstream error\n"); close_vgmstream(vgmstream); return NULL; } -static VGMSTREAM * init_vgmstream_ubi_sb_audio(ubi_sb_header *sb, STREAMFILE *streamTest, STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE *streamData = NULL; +static VGMSTREAM* init_vgmstream_ubi_sb_audio(ubi_sb_header* sb, STREAMFILE* sf_index, STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE* sf_data = NULL; + + if (sb->is_dat) + return init_vgmstream_ubi_dat_main(sb, sf_index, sf); /* open external stream if needed */ if (sb->is_external) { - streamData = open_streamfile_by_filename(streamFile,sb->resource_name); - if (streamData == NULL) { + sf_data = open_streamfile_by_filename(sf, sb->resource_name); + if (sf_data == NULL) { VGM_LOG("UBI SB: external stream '%s' not found\n", sb->resource_name); goto fail; } - } - else { - streamData = streamFile; + } else { + sf_data = sf; } /* init actual VGMSTREAM */ - vgmstream = init_vgmstream_ubi_sb_base(sb, streamTest, streamData, sb->stream_offset); + vgmstream = init_vgmstream_ubi_sb_base(sb, sf_index, sf_data, sb->stream_offset); if (!vgmstream) goto fail; - if (sb->is_external && streamData) close_streamfile(streamData); + if (sf_data != sf) close_streamfile(sf_data); return vgmstream; fail: - if (sb->is_external && streamData) close_streamfile(streamData); + VGM_LOG("UBI SB: init audio error\n"); + if (sf_data != sf) close_streamfile(sf_data); close_vgmstream(vgmstream); return NULL; } -static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *streamTest, STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; +static VGMSTREAM* init_vgmstream_ubi_sb_layer(ubi_sb_header* sb, STREAMFILE* sf_index, STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; layered_layout_data* data = NULL; - STREAMFILE* temp_streamFile = NULL; - STREAMFILE *streamData = NULL; + STREAMFILE* temp_sf = NULL; + STREAMFILE* sf_data = NULL; size_t full_stream_size = sb->stream_size; int i, total_channels = 0; + if (sb->is_ps2_old) { + /* no blocked layout yet, just open it as a normal file */ + return init_vgmstream_ubi_sb_audio(sb, sf_index, sf); + } + /* open external stream if needed */ if (sb->is_external) { - streamData = open_streamfile_by_filename(streamFile,sb->resource_name); - if (streamData == NULL) { + sf_data = open_streamfile_by_filename(sf,sb->resource_name); + if (sf_data == NULL) { VGM_LOG("UBI SB: external stream '%s' not found\n", sb->resource_name); goto fail; } } else { - streamData = streamFile; + sf_data = sf; } /* init layout */ @@ -833,19 +1343,19 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st /* open all layers and mix */ for (i = 0; i < sb->layer_count; i++) { /* prepare streamfile from a single layer section */ - temp_streamFile = setup_ubi_sb_streamfile(streamData, sb->stream_offset, full_stream_size, i, sb->layer_count, sb->big_endian, sb->cfg.layer_hijack); - if (!temp_streamFile) goto fail; + temp_sf = setup_ubi_sb_streamfile(sf_data, sb->stream_offset, full_stream_size, i, sb->layer_count, sb->big_endian, sb->cfg.layer_hijack); + if (!temp_sf) goto fail; - sb->stream_size = get_streamfile_size(temp_streamFile); + sb->stream_size = get_streamfile_size(temp_sf); sb->channels = sb->layer_channels[i]; total_channels += sb->layer_channels[i]; /* build the layer VGMSTREAM (standard sb with custom streamfile) */ - data->layers[i] = init_vgmstream_ubi_sb_base(sb, streamTest, temp_streamFile, 0x00); + data->layers[i] = init_vgmstream_ubi_sb_base(sb, sf_index, temp_sf, 0x00); if (!data->layers[i]) goto fail; - close_streamfile(temp_streamFile); - temp_streamFile = NULL; + close_streamfile(temp_sf); + temp_sf = NULL; } if (!setup_layout_layered(data)) @@ -869,12 +1379,13 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st vgmstream->layout_type = layout_layered; vgmstream->layout_data = data; - if (sb->is_external && streamData) close_streamfile(streamData); + if (sf_data != sf) close_streamfile(sf_data); return vgmstream; fail: - close_streamfile(temp_streamFile); - if (sb->is_external && streamData) close_streamfile(streamData); + VGM_LOG("UBI SB: init layer error\n"); + close_streamfile(temp_sf); + if (sf_data != sf) close_streamfile(sf_data); if (vgmstream) close_vgmstream(vgmstream); else @@ -882,14 +1393,14 @@ fail: return NULL; } -static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE *streamTest, STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; +static VGMSTREAM* init_vgmstream_ubi_sb_sequence(ubi_sb_header* sb, STREAMFILE* sf_index, STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; segmented_layout_data* data = NULL; int i; - STREAMFILE *streamBank = streamTest; + STREAMFILE* sf_bank = sf_index; - //todo optimization: open streamData once / only if new name (doesn't change 99% of the time) + //todo optimization: open sf_data once / only if new name (doesn't change 99% of the time) /* init layout */ data = init_layout_segmented(sb->sequence_count); @@ -906,21 +1417,33 @@ static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE /* bnm sequences may use to entries from other banks, do some voodoo */ - if (sb->is_bnm) { + if (sb->is_bnm || sb->is_dat || sb->is_ps2_bnm) { /* see if *current* bank has changed (may use a different bank N times) */ - if (is_bnm_other_bank(streamBank, sb->sequence_banks[i])) { + if (is_other_bank(sb, sf_bank, sb->sequence_banks[i])) { char bank_name[255]; - sprintf(bank_name, "Bnk_%i.bnm", sb->sequence_banks[i]); - if (streamBank != streamTest) - close_streamfile(streamBank); + if (sf_bank != sf_index) + close_streamfile(sf_bank); - streamBank = open_streamfile_by_filename(streamFile, bank_name); - if (!streamBank) goto fail; + get_ubi_bank_name(sb, sf, sb->sequence_banks[i], bank_name); + sf_bank = open_streamfile_by_filename(sf, bank_name); + + /* may be worth trying in localized folder? */ + //if (!sf_bank) { + // sprintf(bank_name, "English/Bnk_%i.bnm", sb->sequence_banks[i]); + // sf_bank = open_streamfile_by_filename(sf, bank_name); + //} + + if (!sf_bank) { + VGM_LOG("UBI SB: sequence bank %i not found\n", sb->sequence_banks[i]); + goto fail; + } + + //;VGM_LOG("UBI SB: opened %s\n", bank_name); } /* re-parse the thing */ - if (!parse_bnm_header(&temp_sb, streamBank)) + if (!parse_ubi_bank_header(sb, &temp_sb, sf_bank)) goto fail; temp_sb.total_subsongs = 1; /* eh... just to keep parse_header happy */ } @@ -928,18 +1451,24 @@ static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE temp_sb = *sb; /* memcpy'ed */ } + ///* not detectable in .bnm */ + //if (entry_index > temp_sb.total_subsongs) { + // VGM_LOG("UBI SB: wrong sequence entry %i bank index %i (max: %i)\n", i, entry_index, temp_sb.total_subsongs); + // goto fail; + //} + /* parse expected entry */ entry_offset = temp_sb.section2_offset + temp_sb.cfg.section2_entry_size * entry_index; - if (!parse_header(&temp_sb, streamBank, entry_offset, entry_index)) + if (!parse_header(&temp_sb, sf_bank, entry_offset, entry_index)) goto fail; if (temp_sb.type == UBI_NONE || temp_sb.type == UBI_SEQUENCE) { - VGM_LOG("UBI SB: unexpected sequence %i entry type at %x\n", i, (uint32_t)entry_offset); + VGM_LOG("UBI SB: unexpected sequence %i entry type %x at %x\n", i, temp_sb.type, (uint32_t)entry_offset); goto fail; /* not seen, technically ok but too much recursiveness? */ } /* build the layer VGMSTREAM (current sb entry config) */ - data->segments[i] = init_vgmstream_ubi_sb_header(&temp_sb, streamBank, streamFile); + data->segments[i] = init_vgmstream_ubi_sb_header(&temp_sb, sf_bank, sf); if (!data->segments[i]) goto fail; if (i == sb->sequence_loop) @@ -951,8 +1480,8 @@ static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE sb->sample_rate = temp_sb.sample_rate; } - if (streamBank != streamTest) - close_streamfile(streamBank); + if (sf_bank != sf_index) + close_streamfile(sf_bank); if (!setup_layout_segmented(data)) goto fail; @@ -976,47 +1505,48 @@ static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE return vgmstream; fail: + VGM_LOG("UBI SB: init sequence error\n"); if (vgmstream) close_vgmstream(vgmstream); else free_layout_segmented(data); - if (streamBank != streamTest) - close_streamfile(streamBank); + if (sf_bank != sf_index) + close_streamfile(sf_bank); return NULL; } -static size_t silence_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, void* data) { +static size_t silence_io_read(STREAMFILE* sf, uint8_t *dest, off_t offset, size_t length, void* data) { int i; for (i = 0; i < length; i++) { dest[i] = 0; } return length; /* pretend we read zeroes */ } -static size_t silence_io_size(STREAMFILE *streamfile, void* data) { +static size_t silence_io_size(STREAMFILE* sf, void* data) { return 0x7FFFFFF; /* whatevs */ } -static STREAMFILE* setup_silence_streamfile(STREAMFILE *streamFile) { - STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; +static STREAMFILE* setup_silence_streamfile(STREAMFILE* sf) { + STREAMFILE *temp_sf = NULL, *new_sf = NULL; /* setup custom streamfile */ - new_streamFile = open_wrap_streamfile(streamFile); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; + new_sf = open_wrap_streamfile(sf); + if (!new_sf) goto fail; + temp_sf = new_sf; - new_streamFile = open_io_streamfile(temp_streamFile, NULL,0, silence_io_read,silence_io_size); - if (!new_streamFile) goto fail; - temp_streamFile = new_streamFile; + new_sf = open_io_streamfile(temp_sf, NULL,0, silence_io_read,silence_io_size); + if (!new_sf) goto fail; + temp_sf = new_sf; - return temp_streamFile; + return temp_sf; fail: - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); return NULL; } -static VGMSTREAM * init_vgmstream_ubi_sb_silence(ubi_sb_header *sb, STREAMFILE *streamTest, STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE *temp_streamFile = NULL; +static VGMSTREAM* init_vgmstream_ubi_sb_silence(ubi_sb_header* sb, STREAMFILE* sf_index, STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE* temp_sf = NULL; int channel_count, sample_rate; channel_count = sb->channels; @@ -1044,46 +1574,46 @@ static VGMSTREAM * init_vgmstream_ubi_sb_silence(ubi_sb_header *sb, STREAMFILE * vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x02; - temp_streamFile = setup_silence_streamfile(streamFile); - if ( !vgmstream_open_stream(vgmstream, temp_streamFile, 0x00) ) + temp_sf = setup_silence_streamfile(sf); + if ( !vgmstream_open_stream(vgmstream, temp_sf, 0x00) ) goto fail; - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); return vgmstream; fail: close_vgmstream(vgmstream); - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); return vgmstream; } -static VGMSTREAM * init_vgmstream_ubi_sb_header(ubi_sb_header *sb, STREAMFILE* streamTest, STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; +static VGMSTREAM* init_vgmstream_ubi_sb_header(ubi_sb_header* sb, STREAMFILE* sf_index, STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; if (sb->total_subsongs == 0) { VGM_LOG("UBI SB: no subsongs\n"); goto fail; } - ;VGM_LOG("UBI SB: target at %x + %x, extra=%x, name=%s, g=%i, t=%i\n", - (uint32_t)sb->header_offset, sb->cfg.section2_entry_size, (uint32_t)sb->extra_offset, sb->resource_name, sb->group_id, sb->stream_type); + ;VGM_LOG("UBI SB: target at %x + %x, extra=%x, name=%s, sb=%i, t=%i\n", + (uint32_t)sb->header_offset, sb->cfg.section2_entry_size, (uint32_t)sb->extra_offset, sb->resource_name, sb->subblock_id, sb->stream_type); ;VGM_LOG("UBI SB: stream offset=%x, size=%x, name=%s\n", (uint32_t)sb->stream_offset, sb->stream_size, sb->is_external ? sb->resource_name : "internal" ); switch(sb->type) { case UBI_AUDIO: - vgmstream = init_vgmstream_ubi_sb_audio(sb, streamTest, streamFile); + vgmstream = init_vgmstream_ubi_sb_audio(sb, sf_index, sf); break; case UBI_LAYER: - vgmstream = init_vgmstream_ubi_sb_layer(sb, streamTest, streamFile); + vgmstream = init_vgmstream_ubi_sb_layer(sb, sf_index, sf); break; case UBI_SEQUENCE: - vgmstream = init_vgmstream_ubi_sb_sequence(sb, streamTest, streamFile); + vgmstream = init_vgmstream_ubi_sb_sequence(sb, sf_index, sf); break; case UBI_SILENCE: - vgmstream = init_vgmstream_ubi_sb_silence(sb, streamTest, streamFile); + vgmstream = init_vgmstream_ubi_sb_silence(sb, sf_index, sf); break; case UBI_NONE: @@ -1103,7 +1633,7 @@ fail: /* ************************************************************************* */ -static void build_readable_name(char * buf, size_t buf_size, ubi_sb_header * sb) { +static void build_readable_name(char * buf, size_t buf_size, ubi_sb_header* sb) { const char *grp_name; const char *res_name; uint32_t id; @@ -1114,12 +1644,21 @@ static void build_readable_name(char * buf, size_t buf_size, ubi_sb_header * sb) if (sb->is_map) { grp_name = sb->map_name; } - else if (sb->is_bnm) { + else if (sb->is_bnm || sb->is_ps2_bnm) { if (sb->sequence_multibank) grp_name = "bnm-multi"; else grp_name = "bnm"; } + else if (sb->is_dat) { + if (sb->sequence_multibank) + grp_name = "dat-multi"; + else + grp_name = "dat"; + } + else if (sb->is_blk) { + grp_name = "blk"; + } else { grp_name = "bank"; } @@ -1167,52 +1706,186 @@ static void build_readable_name(char * buf, size_t buf_size, ubi_sb_header * sb) } } -static int parse_type_audio(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { - int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; - int16_t (*read_16bit)(off_t,STREAMFILE*) = sb->big_endian ? read_16bitBE : read_16bitLE; +static int parse_type_audio_ps2_bnm(ubi_sb_header *sb, off_t offset, STREAMFILE *sf) { + int32_t(*read_32bit)(off_t, STREAMFILE *) = sb->big_endian ? read_32bitBE : read_32bitLE; + int16_t(*read_16bit)(off_t, STREAMFILE *) = sb->big_endian ? read_16bitBE : read_16bitLE; - /* audio header */ - sb->type = UBI_AUDIO; - - sb->extra_offset = read_32bit(offset + sb->cfg.audio_extra_offset, streamFile) + sb->sectionX_offset; - sb->stream_size = read_32bit(offset + sb->cfg.audio_stream_size, streamFile); - sb->stream_offset = read_32bit(offset + sb->cfg.audio_stream_offset, streamFile); - sb->channels = (sb->cfg.audio_channels % 4) ? /* non-aligned offset is always 16b */ - (uint16_t)read_16bit(offset + sb->cfg.audio_channels, streamFile) : - (uint32_t)read_32bit(offset + sb->cfg.audio_channels, streamFile); - sb->sample_rate = read_32bit(offset + sb->cfg.audio_sample_rate, streamFile); - sb->stream_type = read_32bit(offset + sb->cfg.audio_stream_type, streamFile); + sb->stream_size = read_32bit(offset + sb->cfg.audio_stream_size, sf); + sb->stream_offset = read_32bit(offset + sb->cfg.audio_stream_offset, sf); + sb->channels = read_8bit(offset + sb->cfg.audio_channels, sf); + sb->sample_rate = (uint16_t)read_16bit(offset + sb->cfg.audio_sample_rate, sf); if (sb->stream_size == 0) { VGM_LOG("UBI SB: bad stream size\n"); goto fail; } - if (sb->cfg.audio_external_flag && sb->cfg.audio_external_and) { - sb->is_external = (read_32bit(offset + sb->cfg.audio_external_flag, streamFile) & sb->cfg.audio_external_and); + sb->is_streamed = read_32bit(offset + sb->cfg.audio_streamed_flag, sf) & sb->cfg.audio_streamed_and; + sb->is_cd_streamed = read_32bit(offset + sb->cfg.audio_cd_streamed_flag, sf) & sb->cfg.audio_cd_streamed_and; + sb->loop_flag = read_32bit(offset + sb->cfg.audio_loop_flag, sf) & sb->cfg.audio_loop_and; + + sb->num_samples = 0; /* calculate from size */ + + if (!sb->is_cd_streamed) { + sb->stream_size *= sb->channels; } - if (sb->cfg.audio_group_id && sb->cfg.audio_group_and) { - /* probably means "SW decoded" */ - sb->group_id = read_32bit(offset + sb->cfg.audio_group_id, streamFile); - if (sb->cfg.audio_group_and) sb->group_id &= sb->cfg.audio_group_and; - - /* normalize bitflag, known groups are only id 0/1 (if needed could calculate - * shift-right value here, based on cfg.audio_group_and first 1-bit) */ - if (sb->group_id > 1) - sb->group_id = 1; + if (sb->is_streamed) { + if (sb->is_cd_streamed) { + /* streamed from CD */ + sprintf(sb->resource_name, "BNK_%d.VSC", sb->bank_number); + } else { + /* streamed from RAM */ + sprintf(sb->resource_name, "BNK_%d.VSB", sb->bank_number); + } } else { - /* old group flag (HW decoded?) */ - sb->group_id = (int)!(sb->stream_type & 0x01); + /* loaded fully into SPU memory */ + sprintf(sb->resource_name, "BNK_%d.VB", sb->bank_number); } - if (sb->cfg.audio_loop_flag && sb->cfg.audio_loop_and) { - sb->loop_flag = (read_32bit(offset + sb->cfg.audio_loop_flag, streamFile) & sb->cfg.audio_loop_and); + sb->is_external = 1; + + return 1; +fail: + return 0; +} + +static uint32_t ubi_ps2_pitch_to_freq(uint32_t pitch) { + /* old PS2 games store sample rate in a weird range of 0-65536 remapped from 0-48000 */ + /* strangely, audio res type does have sample rate value but it's unused */ + double sample_rate = (((double)pitch / 65536) * 48000); + return (uint32_t)ceil(sample_rate); +} + +static int parse_type_audio_ps2_old(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) { + int32_t(*read_32bit)(off_t, STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + uint32_t pitch; + uint32_t test_sample_rate; + + sb->stream_size = read_32bit(offset + sb->cfg.audio_stream_size, sf); + sb->stream_offset = read_32bit(offset + sb->cfg.audio_stream_offset, sf); + + if (sb->stream_size == 0) { + VGM_LOG("UBI SB: bad stream size\n"); + goto fail; } + pitch = read_32bit(offset + sb->cfg.audio_pitch, sf); + test_sample_rate = read_32bit(offset + sb->cfg.audio_sample_rate, sf); + sb->sample_rate = ubi_ps2_pitch_to_freq(pitch); + VGM_ASSERT(sb->sample_rate != test_sample_rate, "UBI SB: Converted PS2 sample rate mismatch (%d = %d vs %d)\n", pitch, sb->sample_rate, test_sample_rate); + + sb->is_streamed = read_32bit(offset + sb->cfg.audio_streamed_flag, sf) & sb->cfg.audio_streamed_and; + sb->loop_flag = read_32bit(offset + sb->cfg.audio_loop_flag, sf) & sb->cfg.audio_loop_and; + sb->is_localized = read_32bit(offset + sb->cfg.audio_loc_flag, sf) & sb->cfg.audio_loc_and; + sb->is_stereo = read_32bit(offset + sb->cfg.audio_stereo_flag, sf) & sb->cfg.audio_stereo_and; + + sb->num_samples = 0; /* calculate from size */ + sb->channels = sb->is_stereo ? 2 : 1; + sb->stream_size *= sb->channels; + sb->subblock_id = 0; + + /* filenames are hardcoded */ + if (sb->is_blk) { + blk_get_resource_name(sb); + sb->is_external = 1; + } else if (sb->is_streamed) { + strcpy(sb->resource_name, sb->is_localized ? "STRM.LM1" : "STRM.SM1"); + sb->is_external = 1; + } + + return 1; +fail: + return 0; +} + +static int parse_type_layer_ps2_old(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) { + int32_t(*read_32bit)(off_t, STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + + /* much simpler than later iteration */ + sb->layer_count = read_32bit(offset + sb->cfg.layer_layer_count, sf); + sb->stream_size = read_32bit(offset + sb->cfg.audio_stream_size, sf); + sb->stream_offset = read_32bit(offset + sb->cfg.audio_stream_offset, sf); + + if (sb->stream_size == 0) { + VGM_LOG("UBI SB: bad stream size\n"); + goto fail; + } + + if (sb->layer_count > SB_MAX_LAYER_COUNT) { + VGM_LOG("Ubi SB: incorrect layer count\n"); + goto fail; + } + + sb->sample_rate = ubi_ps2_pitch_to_freq(read_32bit(offset + sb->cfg.layer_pitch, sf)); + sb->is_localized = read_32bit(offset + sb->cfg.layer_loc_flag, sf) & sb->cfg.layer_loc_and; + + sb->num_samples = 0; /* calculate from size */ + sb->channels = sb->layer_count * 2; /* layers are always stereo */ + sb->stream_size *= sb->channels; + + /* filenames are hardcoded */ + if (sb->is_blk) { + blk_get_resource_name(sb); + sb->is_external = 1; + } else if (sb->is_streamed) { + strcpy(sb->resource_name, sb->is_localized ? "STRM.LM1" : "STRM.SM1"); + sb->is_external = 1; + } + + return 1; +fail: + return 0; +} + +static int parse_type_audio(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + int16_t (*read_16bit)(off_t,STREAMFILE*) = sb->big_endian ? read_16bitBE : read_16bitLE; + + /* audio header */ + sb->type = UBI_AUDIO; + + if (sb->is_ps2_bnm) + return parse_type_audio_ps2_bnm(sb, offset, sf); + + if (sb->is_ps2_old) + return parse_type_audio_ps2_old(sb, offset, sf); + + sb->extra_offset = read_32bit(offset + sb->cfg.audio_extra_offset, sf) + sb->sectionX_offset; + sb->stream_size = read_32bit(offset + sb->cfg.audio_stream_size, sf); + sb->stream_offset = read_32bit(offset + sb->cfg.audio_stream_offset, sf); + sb->channels = (sb->cfg.audio_channels % 4) ? /* non-aligned offset is always 16b */ + (uint16_t)read_16bit(offset + sb->cfg.audio_channels, sf) : + (uint32_t)read_32bit(offset + sb->cfg.audio_channels, sf); + sb->sample_rate = read_32bit(offset + sb->cfg.audio_sample_rate, sf); + sb->stream_type = read_32bit(offset + sb->cfg.audio_stream_type, sf); + + if (sb->stream_size == 0) { + VGM_LOG("UBI SB: bad stream size\n"); + goto fail; + } + + sb->is_streamed = read_32bit(offset + sb->cfg.audio_streamed_flag, sf) & sb->cfg.audio_streamed_and; + sb->is_external = sb->is_streamed; + + if (sb->cfg.audio_internal_flag && !sb->is_streamed) { + /* RAM sounds are not always internal in early versions [Donald Duck Demo (PC)] */ + sb->is_external = (int)!(read_32bit(offset + sb->cfg.audio_internal_flag, sf)); + } + + /* apparently, there may also be other subblocks based on various flags but they were not seen so far */ + if (sb->cfg.audio_subblock_flag && sb->cfg.audio_subblock_and) { + int subblock_flag = read_32bit(offset + sb->cfg.audio_subblock_flag, sf) & sb->cfg.audio_subblock_and; + sb->subblock_id = (!subblock_flag) ? 0 : 1; + } else { + sb->subblock_id = (sb->stream_type == 0x01) ? 0 : 1; + } + + sb->loop_flag = read_32bit(offset + sb->cfg.audio_loop_flag, sf) & sb->cfg.audio_loop_and; + if (sb->loop_flag) { - sb->loop_start = read_32bit(offset + sb->cfg.audio_num_samples, streamFile); - sb->num_samples = read_32bit(offset + sb->cfg.audio_num_samples2, streamFile) + sb->loop_start; + sb->loop_start = read_32bit(offset + sb->cfg.audio_num_samples, sf); + sb->num_samples = read_32bit(offset + sb->cfg.audio_num_samples2, sf) + sb->loop_start; if (sb->cfg.audio_num_samples == sb->cfg.audio_num_samples2) { /* early games just repeat and don't set loop start */ sb->num_samples = sb->loop_start; @@ -1222,7 +1895,7 @@ static int parse_type_audio(ubi_sb_header * sb, off_t offset, STREAMFILE* stream * Also rare are looping external streams, since it's normally done through sequences (ex. Surf's Up). * Loop end may be +1? (ex. Splinter Cell: Double Agent PS3 #14331). */ } else { - sb->num_samples = read_32bit(offset + sb->cfg.audio_num_samples, streamFile); + sb->num_samples = read_32bit(offset + sb->cfg.audio_num_samples, sf); } if (sb->cfg.resource_name_size > sizeof(sb->resource_name)) { @@ -1231,17 +1904,21 @@ static int parse_type_audio(ubi_sb_header * sb, off_t offset, STREAMFILE* stream /* external stream name can be found in the header (first versions) or the sectionX table (later versions) */ if (sb->cfg.audio_stream_name) { - read_string(sb->resource_name, sb->cfg.resource_name_size, offset + sb->cfg.audio_stream_name, streamFile); + if (sb->is_dat && !sb->is_external) { + sb->subbank_index = read_8bit(offset + sb->cfg.audio_stream_name + 0x01, sf); + } else { + read_string(sb->resource_name, sb->cfg.resource_name_size, offset + sb->cfg.audio_stream_name, sf); + } } else { - sb->cfg.audio_stream_name = read_32bit(offset + sb->cfg.audio_extra_name, streamFile); - if (sb->cfg.layer_stream_name != 0xFFFFFFFF) - read_string(sb->resource_name, sb->cfg.resource_name_size, sb->sectionX_offset + sb->cfg.audio_stream_name, streamFile); + sb->cfg.audio_stream_name = read_32bit(offset + sb->cfg.audio_extra_name, sf); + if (sb->cfg.audio_stream_name != 0xFFFFFFFF) + read_string(sb->resource_name, sb->cfg.resource_name_size, sb->sectionX_offset + sb->cfg.audio_stream_name, sf); } /* points at XMA1 header in the extra section (only for RAW_XMA1, ignored garbage otherwise) */ if (sb->cfg.audio_xma_offset) { - sb->xma_header_offset = read_32bit(offset + sb->cfg.audio_xma_offset, streamFile) + sb->sectionX_offset; + sb->xma_header_offset = read_32bit(offset + sb->cfg.audio_xma_offset, sf) + sb->sectionX_offset; } return 1; @@ -1249,22 +1926,22 @@ fail: return 0; } -static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { +static int parse_type_sequence(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) { int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; off_t table_offset; int i; /* sequence chain */ sb->type = UBI_SEQUENCE; - if (sb->cfg.sequence_entry_size == 0) { - VGM_LOG("Ubi SB: sequence entry size not configured at %x\n", (uint32_t)offset); + if (sb->cfg.sequence_sequence_count == 0) { + VGM_LOG("Ubi SB: sequence not configured at %x\n", (uint32_t)offset); goto fail; } - sb->extra_offset = read_32bit(offset + sb->cfg.sequence_extra_offset, streamFile) + sb->sectionX_offset; - sb->sequence_loop = read_32bit(offset + sb->cfg.sequence_sequence_loop, streamFile); - sb->sequence_single = read_32bit(offset + sb->cfg.sequence_sequence_single, streamFile); - sb->sequence_count = read_32bit(offset + sb->cfg.sequence_sequence_count, streamFile); + sb->extra_offset = read_32bit(offset + sb->cfg.sequence_extra_offset, sf) + sb->sectionX_offset; + sb->sequence_loop = read_32bit(offset + sb->cfg.sequence_sequence_loop, sf); + sb->sequence_single = read_32bit(offset + sb->cfg.sequence_sequence_single, sf); + sb->sequence_count = read_32bit(offset + sb->cfg.sequence_sequence_count, sf); if (sb->sequence_count > SB_MAX_CHAIN_COUNT) { VGM_LOG("Ubi SB: incorrect sequence count %i vs %i\n", sb->sequence_count, SB_MAX_CHAIN_COUNT); @@ -1274,19 +1951,19 @@ static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* str /* get chain in extra table */ table_offset = sb->extra_offset; for (i = 0; i < sb->sequence_count; i++) { - uint32_t entry_number = (uint32_t)read_32bit(table_offset+sb->cfg.sequence_entry_number, streamFile); + uint32_t entry_number = (uint32_t)read_32bit(table_offset+sb->cfg.sequence_entry_number, sf); /* bnm sequences may refer to entries from different banks, whee */ - if (sb->is_bnm) { + if (sb->is_bnm || sb->is_dat || sb->is_ps2_bnm) { int16_t bank_number = (entry_number >> 16) & 0xFFFF; entry_number = (entry_number >> 00) & 0xFFFF; - //;VGM_LOG("UBI SB: bnm sequence entry=%i, bank=%i\n", entry_number, bank_number); + //;VGM_LOG("UBI SB: bnm sequence entry=%i, bank=%i at %lx\n", entry_number, bank_number, table_offset); sb->sequence_banks[i] = bank_number; /* info flag, does bank number point to another file? */ if (!sb->sequence_multibank) { - sb->sequence_multibank = is_bnm_other_bank(streamFile, bank_number); + sb->sequence_multibank = is_other_bank(sb, sf, bank_number); } } else { @@ -1310,7 +1987,7 @@ fail: return 0; } -static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { +static int parse_type_layer(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) { int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; int16_t (*read_16bit)(off_t,STREAMFILE*) = sb->big_endian ? read_16bitBE : read_16bitLE; off_t table_offset; @@ -1318,15 +1995,21 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream /* layer header */ sb->type = UBI_LAYER; - if (sb->cfg.layer_entry_size == 0) { - VGM_LOG("Ubi SB: layer entry size not configured at %x\n", (uint32_t)offset); + if (sb->cfg.layer_layer_count == 0) { + VGM_LOG("Ubi SB: layers not configured at %x\n", (uint32_t)offset); goto fail; } - sb->extra_offset = read_32bit(offset + sb->cfg.layer_extra_offset, streamFile) + sb->sectionX_offset; - sb->layer_count = read_32bit(offset + sb->cfg.layer_layer_count, streamFile); - sb->stream_size = read_32bit(offset + sb->cfg.layer_stream_size, streamFile); - sb->stream_offset = read_32bit(offset + sb->cfg.layer_stream_offset, streamFile); + /* all layers seem streamed */ + sb->is_streamed = 1; + + if (sb->is_ps2_old) + return parse_type_layer_ps2_old(sb, offset, sf); + + sb->extra_offset = read_32bit(offset + sb->cfg.layer_extra_offset, sf) + sb->sectionX_offset; + sb->layer_count = read_32bit(offset + sb->cfg.layer_layer_count, sf); + sb->stream_size = read_32bit(offset + sb->cfg.layer_stream_size, sf); + sb->stream_offset = read_32bit(offset + sb->cfg.layer_stream_offset, sf); if (sb->stream_size == 0) { VGM_LOG("UBI SB: bad stream size\n"); @@ -1338,22 +2021,24 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream goto fail; } + sb->is_external = sb->is_streamed; + /* get 1st layer header in extra table and validate all headers match */ table_offset = sb->extra_offset; - //sb->channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */ - // (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) : - // (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile); - sb->sample_rate = read_32bit(table_offset + sb->cfg.layer_sample_rate, streamFile); - sb->stream_type = read_32bit(table_offset + sb->cfg.layer_stream_type, streamFile); - sb->num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile); + //sb->channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */ + // (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, sf) : + // (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, sf); + sb->sample_rate = read_32bit(table_offset + sb->cfg.layer_sample_rate, sf); + sb->stream_type = read_32bit(table_offset + sb->cfg.layer_stream_type, sf); + sb->num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, sf); for (i = 0; i < sb->layer_count; i++) { - int channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */ - (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) : - (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile); - int sample_rate = read_32bit(table_offset + sb->cfg.layer_sample_rate, streamFile); - int stream_type = read_32bit(table_offset + sb->cfg.layer_stream_type, streamFile); - int num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile); + int channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */ + (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, sf) : + (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, sf); + int sample_rate = read_32bit(table_offset + sb->cfg.layer_sample_rate, sf); + int stream_type = read_32bit(table_offset + sb->cfg.layer_stream_type, sf); + int num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, sf); if (sb->sample_rate != sample_rate || sb->stream_type != stream_type) { VGM_LOG("Ubi SB: %i layer headers don't match at %x > %x\n", sb->layer_count, (uint32_t)offset, (uint32_t)table_offset); @@ -1372,16 +2057,13 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream table_offset += sb->cfg.layer_entry_size; } - /* all layers seem external */ - sb->is_external = 1; - /* external stream name can be found in the header (first versions) or the sectionX table (later versions) */ if (sb->cfg.layer_stream_name) { - read_string(sb->resource_name, sb->cfg.resource_name_size, offset + sb->cfg.layer_stream_name, streamFile); - } else { - sb->cfg.layer_stream_name = read_32bit(offset + sb->cfg.layer_extra_name, streamFile); + read_string(sb->resource_name, sb->cfg.resource_name_size, offset + sb->cfg.layer_stream_name, sf); + } else if (sb->cfg.layer_extra_name) { + sb->cfg.layer_stream_name = read_32bit(offset + sb->cfg.layer_extra_name, sf); if (sb->cfg.layer_stream_name != 0xFFFFFFFF) - read_string(sb->resource_name, sb->cfg.resource_name_size, sb->sectionX_offset + sb->cfg.layer_stream_name, streamFile); + read_string(sb->resource_name, sb->cfg.resource_name_size, sb->sectionX_offset + sb->cfg.layer_stream_name, sf); } /* layers seem to include XMA header */ @@ -1391,7 +2073,7 @@ fail: return 0; } -static int parse_type_silence(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { +static int parse_type_silence(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) { 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; @@ -1403,11 +2085,11 @@ static int parse_type_silence(ubi_sb_header * sb, off_t offset, STREAMFILE* stre } if (sb->cfg.silence_duration_int) { - uint32_t 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, sf); sb->duration = (float)duration_int / 65536.0f; /* 65536.0 is common so probably means 1.0 */ } else if (sb->cfg.silence_duration_float) { - sb->duration = read_f32(offset + sb->cfg.silence_duration_float, streamFile); + sb->duration = read_f32(offset + sb->cfg.silence_duration_float, sf); } return 1; @@ -1416,7 +2098,7 @@ fail: } // todo improve, only used in bnm sequences as sequence end (and may point to another bnm) -static int parse_type_random(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { +static int parse_type_random(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) { int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; off_t sb_extra_offset, table_offset; @@ -1428,15 +2110,15 @@ static int parse_type_random(ubi_sb_header * sb, off_t offset, STREAMFILE* strea goto fail; } - sb_extra_offset = read_32bit(offset + sb->cfg.random_extra_offset, streamFile) + sb->sectionX_offset; - sb_sequence_count = read_32bit(offset + sb->cfg.random_sequence_count, streamFile); + sb_extra_offset = read_32bit(offset + sb->cfg.random_extra_offset, sf) + sb->sectionX_offset; + sb_sequence_count = read_32bit(offset + sb->cfg.random_sequence_count, sf); /* get chain in extra table */ table_offset = sb_extra_offset; for (i = 0; i < sb_sequence_count; i++) { - uint32_t entry_number = (uint32_t)read_32bit(table_offset+0x00, streamFile); - //uint32_t entry_chance = (uint32_t)read_32bit(table_offset+0x04, streamFile); + uint32_t entry_number = (uint32_t)read_32bit(table_offset+0x00, sf); + //uint32_t entry_chance = (uint32_t)read_32bit(table_offset+0x04, sf); if (sb->is_bnm) { int16_t bank_number = (entry_number >> 16) & 0xFFFF; @@ -1446,7 +2128,7 @@ static int parse_type_random(ubi_sb_header * sb, off_t offset, STREAMFILE* strea //sb->sequence_banks[i] = bank_number; /* not seen */ - if (is_bnm_other_bank(streamFile, bank_number)) { + if (is_other_bank(sb, sf, bank_number)) { VGM_LOG("UBI SB: random in other bank\n"); goto fail; } @@ -1455,7 +2137,7 @@ static int parse_type_random(ubi_sb_header * sb, off_t offset, STREAMFILE* strea //todo make rand or stuff (old chance: int from 0 to 0x10000, new: float from 0.0 to 1.0) { //if (entry_chance == ...) off_t entry_offset = sb->section2_offset + sb->cfg.section2_entry_size * entry_number; - return parse_type_audio(sb, entry_offset, streamFile); + return parse_type_audio(sb, entry_offset, sf); } table_offset += sb->cfg.random_entry_size; @@ -1466,100 +2148,140 @@ fail: return 0; } +static int set_default_codec_for_platform(ubi_sb_header *sb) { + switch (sb->platform) { + case UBI_PC: + sb->codec = RAW_PCM; + break; + case UBI_PS2: + sb->codec = RAW_PSX; + break; + case UBI_PSP: + if (sb->is_psp_old) + sb->codec = FMT_VAG; + else + sb->codec = RAW_PSX; + break; + case UBI_XBOX: + sb->codec = RAW_XBOX; + break; + case UBI_GC: + case UBI_WII: + sb->codec = RAW_DSP; + break; + case UBI_X360: + sb->codec = RAW_XMA1; + break; +#if 0 + case UBI_PS3: /* assumed, but no games seem to use it */ + sb->codec = RAW_AT3; + break; +#endif + case UBI_3DS: + sb->codec = FMT_CWAV; + break; + default: + VGM_LOG("UBI SB: unknown internal format\n"); + return 0; + } + + return 1; +} /* find actual codec from type (as different games' stream_type can overlap) */ -static int parse_stream_codec(ubi_sb_header * sb) { +static int parse_stream_codec(ubi_sb_header* sb) { if (sb->type == UBI_SEQUENCE) return 1; - /* in early versions, this is a bitfield with either 1 or 2 rightmost bits being flags */ - sb->stream_type >>= sb->cfg.num_codec_flags; + if (sb->is_dat) { + /* handled separately */ + return 1; + } - if (sb->cfg.default_codec_for_group0 && sb->type == UBI_AUDIO && sb->group_id == 0) { - /* early Xbox games contain garbage in stream_type field in this case, it seems that 0x00 is assumed */ - sb->stream_type = 0x00; + if (sb->is_ps2_bnm || sb->is_ps2_old) { + /* early PS2 games don't support different codecs, it's always PSX ADPCM */ + sb->codec = RAW_PSX; + return 1; } /* guess codec */ - if (sb->stream_type == 0x00) { - switch (sb->platform) { - case UBI_PC: - sb->codec = RAW_PCM; - break; - case UBI_PS2: - sb->codec = RAW_PSX; - break; - case UBI_PSP: - if (sb->is_psp_old) - sb->codec = FMT_VAG; - else - sb->codec = RAW_PSX; - break; - case UBI_XBOX: - sb->codec = RAW_XBOX; - break; - case UBI_GC: - case UBI_WII: - sb->codec = RAW_DSP; - break; - case UBI_X360: - sb->codec = RAW_XMA1; - break; -#if 0 - case UBI_PS3: /* assumed, but no games seem to use it */ - sb->codec = RAW_AT3; - break; -#endif - case UBI_3DS: - sb->codec = FMT_CWAV; - break; - default: - VGM_LOG("UBI SB: unknown internal format\n"); - goto fail; - } - } else if (sb->version == 0x00000000) { - /* really old version predating SBx and SMx formats */ - /* Rayman 2: The Great Escape */ - /* Tonic Trouble */ - /* Donald Duck: Goin' Quackers */ - /* Disney's Dinosaur */ - + if (sb->is_bnm || sb->version < 0x00000007) { /* bnm is ~v0 but some games have wonky versions */ switch (sb->stream_type) { case 0x01: + if (!set_default_codec_for_platform(sb)) + goto fail; + break; + + case 0x02: sb->codec = FMT_MPDX; break; - - case 0x02: + + case 0x04: sb->codec = FMT_APM; break; + case 0x06: /* The Jungle Book (internal extension is .adp, maybe Ubi ADPCM can be considered FMT_ADP) */ + sb->codec = UBI_ADPCM; + break; + + case 0x08: + sb->codec = UBI_IMA; /* Ubi IMA v2/v3 */ + break; + default: - VGM_LOG("UBI SB: Unknown stream_type %02x for version %08x\n", sb->stream_type, sb->version); + VGM_LOG("UBI SB: unknown stream_type %02x for version %08x\n", sb->stream_type, sb->version); goto fail; } } else if (sb->version < 0x000A0000) { switch (sb->stream_type) { case 0x01: - sb->codec = UBI_ADPCM; + if (!set_default_codec_for_platform(sb)) + goto fail; break; case 0x02: + sb->codec = UBI_ADPCM; + break; +#if 0 + case 0x03: + sb->codec = FMT_MPDX; /* not seen yet, some MPEG based codec */ + break; +#endif + case 0x04: sb->codec = UBI_IMA; /* Ubi IMA v2/v3 */ break; - +#if 0 + case 0x05: + sb->codec = FMT_OGG; /* not seen yet */ + break; +#endif default: VGM_LOG("UBI SB: Unknown stream_type %02x for version %08x\n", sb->stream_type, sb->version); goto fail; } - } else { + } else { + /* some Xbox games default to codec 0 if subblock flag isn't set while the actual field contains garbage */ + if (sb->cfg.default_codec_for_subblock0 && sb->type == UBI_AUDIO && sb->subblock_id == 0) { + sb->stream_type = 0x00; + } + switch (sb->stream_type) { + case 0x00: + if (!set_default_codec_for_platform(sb)) + goto fail; + break; + case 0x01: sb->codec = RAW_PCM; /* uncommon, ex. Wii/PSP/3DS */ break; case 0x02: switch (sb->platform) { + case UBI_PC: + case UBI_XBOX: + sb->codec = UBI_ADPCM; + break; case UBI_PS3: sb->codec = RAW_PSX; /* PS3 */ break; @@ -1567,8 +2289,8 @@ static int parse_stream_codec(ubi_sb_header * sb) { sb->codec = UBI_IMA_SCE; break; default: - sb->codec = UBI_ADPCM; - break; + VGM_LOG("UBI SB: unknown codec for stream_type %02x\n", sb->stream_type); + goto fail; } break; @@ -1604,7 +2326,7 @@ static int parse_stream_codec(ubi_sb_header * sb) { break; case 0x08: - sb->codec = FMT_AT3; + sb->codec = FMT_AT3; /* PS3 */ break; default: @@ -1619,20 +2341,38 @@ fail: } /* find actual stream offset in section3 */ -static int parse_offsets(ubi_sb_header * sb, STREAMFILE *streamFile) { +static int parse_offsets(ubi_sb_header* sb, STREAMFILE* sf) { int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; - int i, j, k; + uint32_t i, j, k; if (sb->type == UBI_SEQUENCE) return 1; + if (sb->is_bnm) + return bnm_parse_offsets(sb, sf); + + /* handled separately */ + if (sb->is_dat) + return 1; + + if (sb->is_ps2_bnm) { + if (sb->is_cd_streamed) { + /* offsets for CD streams are stored in sectors */ + sb->stream_offset *= 0x800; + } + return 1; + } + + if (sb->is_blk) + return blk_parse_offsets(sb); + VGM_ASSERT(!sb->is_map && sb->section3_num > 2, "UBI SB: section3 > 2 found\n"); if (sb->is_external) return 1; - /* Internal sounds are split into codec groups, with their offsets being relative to group start. - * A table contains sizes of each group, so we adjust offsets based on the group ID of our sound. + /* Internal sounds are split into subblocks, with their offsets being relative to subblock start. + * A table contains sizes of each subblock, so we adjust offsets based on the subblock ID of our sound. * Headers normally only use 0 or 1, and section3 may only define id1 (which the internal sound would use). * May exist even for external streams only, and they often use id 1 too. */ @@ -1645,34 +2385,40 @@ static int parse_offsets(ubi_sb_header * sb, STREAMFILE *streamFile) { * 0x0c: table 2 offset * 0x10: table 2 entries * table 1 - for each entry: - * 0x00: sec2 entry index - * 0x04: sound offset + * 0x00: sec2 entry index + * 0x04: sound offset * table 2 - for each entry: - * 0x00 - group ID - * 0x04 - size with padding included - * 0x08 - size without padding - * 0x0c - absolute group offset */ + * 0x00 - subblock ID + * 0x04 - size with padding included + * 0x08 - size without padding + * 0x0c - absolute subblock offset + */ for (i = 0; i < sb->section3_num; i++) { off_t offset = sb->section3_offset + 0x14 * i; - off_t table_offset = read_32bit(offset + 0x04, streamFile) + sb->section3_offset; - uint32_t table_num = read_32bit(offset + 0x08, streamFile); - off_t table2_offset = read_32bit(offset + 0x0c, streamFile) + sb->section3_offset; - uint32_t table2_num = read_32bit(offset + 0x10, streamFile); + off_t table_offset = read_32bit(offset + 0x04, sf) + sb->section3_offset; + uint32_t table_num = read_32bit(offset + 0x08, sf); + off_t table2_offset = read_32bit(offset + 0x0c, sf) + sb->section3_offset; + uint32_t table2_num = read_32bit(offset + 0x10, sf); for (j = 0; j < table_num; j++) { - int index = read_32bit(table_offset + 0x08 * j + 0x00, streamFile) & 0x0000FFFF; + int index = read_32bit(table_offset + 0x08 * j + 0x00, sf) & 0x3FFFFFFF; if (index == sb->header_index) { - sb->stream_offset = read_32bit(table_offset + 0x08 * j + 0x04, streamFile); + sb->stream_offset = read_32bit(table_offset + 0x08 * j + 0x04, sf); for (k = 0; k < table2_num; k++) { - uint32_t id = read_32bit(table2_offset + 0x10 * k + 0x00, streamFile); + uint32_t id = read_32bit(table2_offset + 0x10 * k + 0x00, sf); - if (id == sb->group_id) { - sb->stream_offset += read_32bit(table2_offset + 0x10 * k + 0x0c, streamFile); + if (id == sb->subblock_id) { + sb->stream_offset += read_32bit(table2_offset + 0x10 * k + 0x0c, sf); break; } } + + if (k == table2_num) { + VGM_LOG("UBI SM: Failed to find subblock %d in map %s\n", sb->subblock_id, sb->map_name); + goto fail; + } break; } } @@ -1680,9 +2426,13 @@ static int parse_offsets(ubi_sb_header * sb, STREAMFILE *streamFile) { if (sb->stream_offset) break; } - } - else { - /* banks store internal sounds after all headers and adjusted by the group table, find the matching entry */ + + if (sb->stream_offset == 0) { + VGM_LOG("UBI SM: Failed to find offset for resource %d in subblock %d in map %s\n", sb->header_index, sb->subblock_id, sb->map_name); + goto fail; + } + } else { + /* banks store internal sounds after all headers and adjusted by the subblock table, find the matching entry */ off_t sounds_offset = sb->section3_offset + sb->cfg.section3_entry_size*sb->section3_num; if (sb->cfg.is_padded_sounds_offset) @@ -1694,51 +2444,51 @@ static int parse_offsets(ubi_sb_header * sb, STREAMFILE *streamFile) { off_t offset = sb->section3_offset + sb->cfg.section3_entry_size * i; /* table has unordered ids+size, so if our id doesn't match current data offset must be beyond */ - if (read_32bit(offset + 0x00, streamFile) == sb->group_id) + if (read_32bit(offset + 0x00, sf) == sb->subblock_id) break; - sb->stream_offset += read_32bit(offset + 0x04, streamFile); + sb->stream_offset += read_32bit(offset + 0x04, sf); } } } return 1; -//fail: -// return 0; +fail: + return 0; } /* parse a single known header resource at offset (see config_sb for info) */ -static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset, int index) { +static int parse_header(ubi_sb_header* sb, STREAMFILE* sf, off_t offset, int index) { int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; sb->header_index = index; sb->header_offset = offset; - sb->header_id = read_32bit(offset + 0x00, streamFile); - sb->header_type = read_32bit(offset + 0x04, streamFile); + sb->header_id = read_32bit(offset + 0x00, sf); + sb->header_type = read_32bit(offset + 0x04, sf); switch(sb->header_type) { case 0x01: - if (!parse_type_audio(sb, offset, streamFile)) + if (!parse_type_audio(sb, offset, sf)) goto fail; break; case 0x05: case 0x0b: case 0x0c: - if (!parse_type_sequence(sb, offset, streamFile)) + if (!parse_type_sequence(sb, offset, sf)) goto fail; break; case 0x06: case 0x0d: - if (!parse_type_layer(sb, offset, streamFile)) + if (!parse_type_layer(sb, offset, sf)) goto fail; break; case 0x08: case 0x0f: - if (!parse_type_silence(sb, offset, streamFile)) + if (!parse_type_silence(sb, offset, sf)) goto fail; break; case 0x0a: - if (!parse_type_random(sb, offset, streamFile)) + if (!parse_type_random(sb, offset, sf)) goto fail; break; default: @@ -1749,7 +2499,7 @@ static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset if (!parse_stream_codec(sb)) goto fail; - if (!parse_offsets(sb, streamFile)) + if (!parse_offsets(sb, sf)) goto fail; return 1; @@ -1758,7 +2508,7 @@ fail: } /* parse a bank and its possible audio headers */ -static int parse_sb(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subsong) { +static int parse_sb(ubi_sb_header* sb, STREAMFILE* sf, int target_subsong) { int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; int i; @@ -1772,10 +2522,10 @@ static int parse_sb(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subso off_t offset = sb->section2_offset + sb->cfg.section2_entry_size*i; uint32_t header_type; - /*header_id =*/ read_32bit(offset + 0x00, streamFile); /* forces buffer read */ - header_type = read_32bit(offset + 0x04, streamFile); + /*header_id =*/ read_32bit(offset + 0x00, sf); /* forces buffer read */ + header_type = read_32bit(offset + 0x04, sf); - if (header_type <= 0x00 || header_type >= 0x10) { + if (header_type >= 0x10) { VGM_LOG("UBI SB: unknown type %x at %x\n", header_type, (uint32_t)offset); goto fail; } @@ -1789,7 +2539,7 @@ static int parse_sb(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subso if (sb->total_subsongs != target_subsong) continue; - if (!parse_header(sb, streamFile, offset, i)) + if (!parse_header(sb, sf, offset, i)) goto fail; build_readable_name(sb->readable_name, sizeof(sb->readable_name), sb); @@ -1806,17 +2556,17 @@ fail: /* ************************************************************************* */ -static int config_sb_platform(ubi_sb_header * sb, STREAMFILE *streamFile) { +static int config_sb_platform(ubi_sb_header* sb, STREAMFILE* sf) { char filename[PATH_LIMIT]; int filename_len; char platform_char; uint32_t version; /* to find out hijacking (LE) platforms */ - version = read_32bitLE(0x00, streamFile); + version = read_32bitLE(0x00, sf); /* get X from .sbX/smX/lmX */ - get_streamfile_name(streamFile,filename,sizeof(filename)); + get_streamfile_name(sf,filename,sizeof(filename)); filename_len = strlen(filename); platform_char = filename[filename_len - 1]; @@ -1876,30 +2626,52 @@ fail: } -static void config_sb_entry(ubi_sb_header * sb, size_t section1_size_entry, size_t section2_size_entry) { +static void config_sb_entry(ubi_sb_header* sb, size_t section1_size_entry, size_t section2_size_entry) { sb->cfg.section1_entry_size = section1_size_entry; sb->cfg.section2_entry_size = section2_size_entry; sb->cfg.section3_entry_size = 0x08; } -static void config_sb_audio_fs(ubi_sb_header * sb, off_t external_flag, off_t group_id, off_t loop_flag) { +static void config_sb_audio_fs(ubi_sb_header* sb, off_t streamed_flag, off_t subblock_flag, off_t loop_flag) { /* audio header with standard flags */ - sb->cfg.audio_external_flag = external_flag; - sb->cfg.audio_group_id = group_id; + sb->cfg.audio_streamed_flag = streamed_flag; + sb->cfg.audio_subblock_flag = subblock_flag; sb->cfg.audio_loop_flag = loop_flag; - sb->cfg.audio_external_and = 1; - sb->cfg.audio_group_and = 1; + sb->cfg.audio_streamed_and = 1; + sb->cfg.audio_subblock_and = 1; sb->cfg.audio_loop_and = 1; } -static void config_sb_audio_fb(ubi_sb_header * sb, off_t flag_bits, int external_and, int group_and, int loop_and) { +static void config_sb_audio_fb(ubi_sb_header* sb, off_t flag_bits, int streamed_and, int subblock_and, int loop_and) { /* audio header with bit flags */ - sb->cfg.audio_external_flag = flag_bits; - sb->cfg.audio_group_id = flag_bits; + sb->cfg.audio_streamed_flag = flag_bits; + sb->cfg.audio_subblock_flag = flag_bits; sb->cfg.audio_loop_flag = flag_bits; - sb->cfg.audio_external_and = external_and; - sb->cfg.audio_group_and = group_and; + sb->cfg.audio_streamed_and = streamed_and; + sb->cfg.audio_subblock_and = subblock_and; sb->cfg.audio_loop_and = loop_and; } -static void config_sb_audio_hs(ubi_sb_header * sb, off_t channels, off_t sample_rate, off_t num_samples, off_t num_samples2, off_t stream_name, off_t stream_type) { +static void config_sb_audio_fb_ps2_bnm(ubi_sb_header *sb, off_t flag_bits, int streamed_and, int cd_streamed_and, int loop_and) { + /* audio header with standard flags */ + sb->cfg.audio_streamed_flag = flag_bits; + sb->cfg.audio_cd_streamed_flag = flag_bits; + sb->cfg.audio_loop_flag = flag_bits; + sb->cfg.audio_streamed_and = streamed_and; + sb->cfg.audio_cd_streamed_and = cd_streamed_and; + sb->cfg.audio_loop_and = loop_and; +} +static void config_sb_audio_ps2_old(ubi_sb_header* sb, off_t flag_bits, int streamed_and, int loop_and, int loc_and, int stereo_and, off_t pitch, off_t sample_rate) { + /* sample rate only, bit flags */ + sb->cfg.audio_streamed_flag = flag_bits; + sb->cfg.audio_loop_flag = flag_bits; + sb->cfg.audio_loc_flag = flag_bits; + sb->cfg.audio_stereo_flag = flag_bits; + sb->cfg.audio_streamed_and = streamed_and; + sb->cfg.audio_loop_and = loop_and; + sb->cfg.audio_loc_and = loc_and; + sb->cfg.audio_stereo_and = stereo_and; + sb->cfg.audio_pitch = pitch; + sb->cfg.audio_sample_rate = sample_rate; +} +static void config_sb_audio_hs(ubi_sb_header* sb, off_t channels, off_t sample_rate, off_t num_samples, off_t num_samples2, off_t stream_name, off_t stream_type) { /* audio header with stream name */ sb->cfg.audio_channels = channels; sb->cfg.audio_sample_rate = sample_rate; @@ -1908,7 +2680,7 @@ static void config_sb_audio_hs(ubi_sb_header * sb, off_t channels, off_t sample_ sb->cfg.audio_stream_name = stream_name; sb->cfg.audio_stream_type = stream_type; } -static void config_sb_audio_he(ubi_sb_header * sb, off_t channels, off_t sample_rate, off_t num_samples, off_t num_samples2, off_t extra_name, off_t stream_type) { +static void config_sb_audio_he(ubi_sb_header* sb, off_t channels, off_t sample_rate, off_t num_samples, off_t num_samples2, off_t extra_name, off_t stream_type) { /* audio header with extra name */ sb->cfg.audio_channels = channels; sb->cfg.audio_sample_rate = sample_rate; @@ -1917,33 +2689,37 @@ static void config_sb_audio_he(ubi_sb_header * sb, off_t channels, off_t sample_ sb->cfg.audio_extra_name = extra_name; sb->cfg.audio_stream_type = stream_type; } -static void config_sb_sequence(ubi_sb_header * sb, off_t sequence_count, off_t entry_size) { +static void config_sb_sequence(ubi_sb_header* sb, off_t sequence_count, off_t entry_size) { /* sequence header and chain table */ sb->cfg.sequence_sequence_loop = sequence_count - 0x10; sb->cfg.sequence_sequence_single= sequence_count - 0x0c; sb->cfg.sequence_sequence_count = sequence_count; sb->cfg.sequence_entry_size = entry_size; sb->cfg.sequence_entry_number = 0x00; - if (sb->is_bnm) { + if (sb->is_bnm || sb->is_dat || sb->is_ps2_bnm) { sb->cfg.sequence_sequence_loop = sequence_count - 0x0c; sb->cfg.sequence_sequence_single= sequence_count - 0x08; } + if (sb->is_blk) { + sb->cfg.sequence_sequence_loop = sequence_count - 0x14; + sb->cfg.sequence_sequence_single= sequence_count - 0x0c; + } } -static void config_sb_layer_hs(ubi_sb_header * sb, off_t layer_count, off_t stream_size, off_t stream_offset, off_t stream_name) { +static void config_sb_layer_hs(ubi_sb_header* sb, off_t layer_count, off_t stream_size, off_t stream_offset, off_t stream_name) { /* layer headers with stream name */ sb->cfg.layer_layer_count = layer_count; sb->cfg.layer_stream_size = stream_size; sb->cfg.layer_stream_offset = stream_offset; sb->cfg.layer_stream_name = stream_name; } -static void config_sb_layer_he(ubi_sb_header * sb, off_t layer_count, off_t stream_size, off_t stream_offset, off_t extra_name) { +static void config_sb_layer_he(ubi_sb_header* sb, off_t layer_count, off_t stream_size, off_t stream_offset, off_t extra_name) { /* layer headers with extra name */ sb->cfg.layer_layer_count = layer_count; sb->cfg.layer_stream_size = stream_size; sb->cfg.layer_stream_offset = stream_offset; sb->cfg.layer_extra_name = extra_name; } -static void config_sb_layer_sh(ubi_sb_header * sb, off_t entry_size, off_t sample_rate, off_t channels, off_t stream_type, off_t num_samples) { +static void config_sb_layer_sh(ubi_sb_header* sb, off_t entry_size, off_t sample_rate, off_t channels, off_t stream_type, off_t num_samples) { /* layer sub-headers in extra table */ sb->cfg.layer_entry_size = entry_size; sb->cfg.layer_sample_rate = sample_rate; @@ -1951,23 +2727,29 @@ static void config_sb_layer_sh(ubi_sb_header * sb, off_t entry_size, off_t sampl sb->cfg.layer_stream_type = stream_type; sb->cfg.layer_num_samples = num_samples; } -static void config_sb_silence_i(ubi_sb_header * sb, off_t duration) { +static void config_sb_layer_ps2_old(ubi_sb_header *sb, off_t loc_flag, int loc_and, off_t layer_count, off_t pitch) { + /* no name, no layer headers */ + sb->cfg.layer_loc_flag = loc_flag; + sb->cfg.layer_loc_and = loc_and; + sb->cfg.layer_layer_count = layer_count; + sb->cfg.layer_pitch = pitch; +} +static void config_sb_silence_i(ubi_sb_header* sb, off_t duration) { /* silence headers in int value */ sb->cfg.silence_duration_int = duration; } -static void config_sb_silence_f(ubi_sb_header * sb, off_t duration) { +static void config_sb_silence_f(ubi_sb_header* sb, off_t duration) { /* silence headers in float value */ sb->cfg.silence_duration_float = duration; } -static void config_sb_random_old(ubi_sb_header * sb, off_t sequence_count, off_t entry_size) { +static void config_sb_random_old(ubi_sb_header* sb, off_t sequence_count, off_t entry_size) { sb->cfg.random_sequence_count = sequence_count; sb->cfg.random_entry_size = entry_size; sb->cfg.random_percent_int = 1; } -static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { - int is_dino_pc = 0; +static int config_sb_version(ubi_sb_header* sb, STREAMFILE* sf) { int is_ttse_pc = 0; int is_bia_ps2 = 0, is_biadd_psp = 0; int is_sc2_ps2_gc = 0; @@ -2085,8 +2867,12 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { sb->cfg.map_version = 3; sb->cfg.map_entry_size = (sb->cfg.map_version < 2) ? 0x30 : 0x34; + if (sb->is_blk) { + sb->cfg.map_entry_size = 0x30; + } - if (sb->version == 0x00000000 || sb->version == 0x00000200) { + if (sb->is_bnm || sb->is_blk || sb->is_dat) { + sb->cfg.audio_internal_flag = 0x08; sb->cfg.audio_stream_size = 0x0c; sb->cfg.audio_stream_offset = 0x10; //sb->cfg.audio_extra_offset = 0x10; @@ -2098,9 +2884,19 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { //sb->cfg.layer_extra_offset = 0x10; //sb->cfg.layer_extra_size = 0x0c; + sb->cfg.random_extra_offset = 0x10; + //sb->cfg.random_extra_size = 0x0c; + } else if (sb->is_ps2_bnm) { + sb->cfg.audio_stream_size = 0x2c; + sb->cfg.audio_stream_offset = 0x30; + + sb->cfg.sequence_extra_offset = 0x10; + //sb->cfg.sequence_extra_size = 0x0c; + sb->cfg.random_extra_offset = 0x10; //sb->cfg.random_extra_size = 0x0c; } else if (sb->version <= 0x00000007) { + sb->cfg.audio_internal_flag = 0x08; sb->cfg.audio_stream_size = 0x0c; sb->cfg.audio_extra_offset = 0x10; sb->cfg.audio_stream_offset = 0x14; @@ -2118,14 +2914,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { sb->cfg.layer_extra_offset = 0x0c; } - if (sb->version == 0x00000000 || sb->version == 0x00000200) { - sb->cfg.num_codec_flags = 1; - } else if (sb->version <= 0x00000004) { - sb->cfg.num_codec_flags = 2; - } else if (sb->version < 0x000A0000) { - sb->cfg.num_codec_flags = 1; - } - sb->allowed_types[0x01] = 1; sb->allowed_types[0x05] = 1; sb->allowed_types[0x0c] = 1; @@ -2133,7 +2921,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { sb->allowed_types[0x0d] = 1; //sb->allowed_types[0x08] = 1; /* only needed inside sequences */ //sb->allowed_types[0x0f] = 1; - if (sb->is_bnm) { + if (sb->is_bnm || sb->is_dat || sb->is_ps2_bnm) { //sb->allowed_types[0x0a] = 1; /* only needed inside sequences */ sb->allowed_types[0x0b] = 1; sb->allowed_types[0x09] = 1; @@ -2141,45 +2929,46 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { #if 0 { - STREAMFILE * streamTest; - streamTest= open_streamfile_by_filename(streamFile, ".no_audio.sbx"); - if (streamTest) { sb->allowed_types[0x01] = 0; close_streamfile(streamTest); } + STREAMFILE* test_sf; + test_sf= open_streamfile_by_filename(sf, ".no_audio.sbx"); + if (test_sf) { sb->allowed_types[0x01] = 0; close_streamfile(test_sf); } - streamTest= open_streamfile_by_filename(streamFile, ".no_sequence.sbx"); - if (streamTest) { sb->allowed_types[0x05] = sb->allowed_types[0x0c] = 0; close_streamfile(streamTest); } + test_sf= open_streamfile_by_filename(sf, ".no_sequence.sbx"); + if (test_sf) { sb->allowed_types[0x05] = sb->allowed_types[0x0c] = 0; close_streamfile(test_sf); } - streamTest= open_streamfile_by_filename(streamFile, ".no_layer.sbx"); - if (streamTest) { sb->allowed_types[0x06] = sb->allowed_types[0x0d] = 0; close_streamfile(streamTest); } + test_sf= open_streamfile_by_filename(sf, ".no_layer.sbx"); + if (test_sf) { sb->allowed_types[0x06] = sb->allowed_types[0x0d] = 0; close_streamfile(test_sf); } } #endif /* two configs with same id; use SND file as identifier */ if (sb->version == 0x00000000 && sb->platform == UBI_PC) { - STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "Dino.lcb"); - if (streamTest) { - is_dino_pc = 1; - close_streamfile(streamTest); + STREAMFILE* test_sf = open_streamfile_by_filename(sf, "Dino.lcb"); + if (test_sf) { + sb->version = 0x00000200; /* some files in Dinosaur use this, probably final version */ + close_streamfile(test_sf); } } - /* some files in Dinosaur */ - if (sb->version == 0x00000200 && sb->platform == UBI_PC) { + /* memory garbage found in F1 Racing Simulation */ + if ((sb->version == 0xAAAAAAAA && sb->platform == UBI_PC) || + (sb->version == 0xCDCDCDCD && sb->platform == UBI_PC)) { sb->version = 0x00000000; - is_dino_pc = 1; } + /* Tonic Touble beta has garbage instead of version */ if (sb->is_bnm && sb->version > 0x00000000 && sb->platform == UBI_PC) { - STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "ED_MAIN.LCB"); - if (streamTest) { + STREAMFILE* test_sf = open_streamfile_by_filename(sf, "ED_MAIN.LCB"); + if (test_sf) { is_ttse_pc = 1; sb->version = 0x00000000; - close_streamfile(streamTest); + close_streamfile(test_sf); } } - /* Tonic Trouble Special Edition (1999)(PC)-bnm */ + /* Tonic Trouble Special Edition (1998)(PC)-bnm */ if (sb->version == 0x00000000 && sb->platform == UBI_PC && is_ttse_pc) { config_sb_entry(sb, 0x20, 0x5c); @@ -2197,13 +2986,16 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - + /* F1 Racing Simulation (1997)(PC)-bnm [not TTSE version] */ /* Rayman 2: The Great Escape (1999)(PC)-bnm */ /* Tonic Trouble (1999)(PC)-bnm */ /* Donald Duck: Goin' Quackers (2000)(PC)-bnm */ /* Disney's Dinosaur (2000)(PC)-bnm */ - if (sb->version == 0x00000000 && sb->platform == UBI_PC) { + if ((sb->version == 0x00000000 && sb->platform == UBI_PC) || + (sb->version == 0x00000200 && sb->platform == UBI_PC)) { config_sb_entry(sb, 0x20, 0x5c); + if (sb->version == 0x00000200) + config_sb_entry(sb, 0x20, 0x60); config_sb_audio_fs(sb, 0x2c, 0x00, 0x30); config_sb_audio_hs(sb, 0x42, 0x3c, 0x34, 0x34, 0x48, 0x44); @@ -2214,9 +3006,69 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_random_old(sb, 0x18, 0x0c); /* Rayman 2 needs it for rare sequence ends (ex. Bnk_31.bnm) */ /* no layers */ + return 1; + } - if (is_dino_pc) - config_sb_entry(sb, 0x20, 0x60); + /* The Jungle Book: Rhythm N'Groove (2000)(PC)-bnm */ + if (sb->version == 0x00060409 && sb->platform == UBI_PC) { + config_sb_entry(sb, 0x24, 0x64); + + config_sb_audio_fs(sb, 0x2c, 0x00, 0x30); + config_sb_audio_hs(sb, 0x4E, 0x48, 0x34, 0x34, 0x54, 0x50); + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x2c, 0x1c); + + /* no layers */ + + return 1; + } + + /* not again... */ + if (sb->version == 0x00000000 && sb->platform == UBI_DC) { + /* check if there's a matching KAT, crap but works */ + STREAMFILE *test_sf = open_streamfile_by_ext(sf, "kat"); + if (test_sf) { + sb->version = 0x00000200; /* assumed */ + close_streamfile(test_sf); + } + } + + /* Rayman 2: The Great Escape (2000)(DC)-dat */ + /* Donald Duck: Goin' Quackers (2000)(DC)-dat */ + /* Disney's Dinosaur (2000)(DC)-dat */ + if ((sb->version == 0x00000000 && sb->platform == UBI_DC) || + (sb->version == 0x00000200 && sb->platform == UBI_DC)) { + config_sb_entry(sb, 0x20, 0x64); + if (sb->version == 0x00000200) + config_sb_entry(sb, 0x20, 0x68); + + config_sb_audio_fs(sb, 0x2c, 0x00, 0x30); + config_sb_audio_hs(sb, 0x42, 0x3c, 0x34, 0x34, 0x48, 0x44); + /* has internal names but they're partially overwritten by sound index */ + + config_sb_sequence(sb, 0x24, 0x18); + + config_sb_random_old(sb, 0x18, 0x0c); + + /* no layers */ + return 1; + } + + /* Rayman 2: Revolution (2000)(PS2)-bnm */ + /* Disney's Dinosaur (2000)(PS2)-bnm */ + /* Hype: The Time Quest (2001)(PS2)-bnm */ + if (sb->version == 0x32787370 && sb->platform == UBI_PS2 && sb->is_ps2_bnm) { + sb->version = 0x00000000; /* for convenience */ + config_sb_entry(sb, 0x1c, 0x44); + + config_sb_audio_fb_ps2_bnm(sb, 0x18, (1 << 5), (1 << 6), (1 << 7)); + config_sb_audio_hs(sb, 0x20, 0x22, 0x00, 0x00, 0x00, 0x1c); + sb->cfg.audio_interleave = 0x400; + + config_sb_sequence(sb, 0x24, 0x14); + + /* no layers */ return 1; } @@ -2237,6 +3089,36 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } + /* Donald Duck: Goin' Quackers (2000)(PS2)-blk */ + /* The Jungle Book: Rhythm N'Groove (2001)(PS2)-blk */ + if (sb->version == 0x00000003 && sb->platform == UBI_PS2 && sb->is_blk) { + config_sb_entry(sb, 0x20, 0x40); + + config_sb_audio_ps2_old(sb, 0x18, (1 << 4), (1 << 5), (1 << 6), (1 << 7), 0x1c, 0x20); + sb->cfg.audio_interleave = 0x800; + sb->is_ps2_old = 1; /* yikes */ + + config_sb_sequence(sb, 0x2c, 0x18); /* this is normal enough */ + + config_sb_layer_ps2_old(sb, 0x18, (1 << 0), 0x1c, 0x20); + return 1; + } + + /* Batman: Vengeance (2001)(PS2)-map */ + /* Disney's Tarzan: Untamed (2001)(PS2)-map */ + if (sb->version == 0x00000003 && sb->platform == UBI_PS2) { + config_sb_entry(sb, 0x30, 0x3c); + + config_sb_audio_ps2_old(sb, 0x1c, (1 << 4), (1 << 5), (1 << 6), (1 << 7), 0x20, 0x24); + sb->cfg.audio_interleave = 0x800; + sb->is_ps2_old = 1; + + config_sb_sequence(sb, 0x2c, 0x18); + + config_sb_layer_ps2_old(sb, 0x1c, (1 << 0), 0x20, 0x24); + return 1; + } + /* Disney's Tarzan: Untamed (2001)(GC)-map */ /* Batman: Vengeance (2001)(GC)-map */ /* Donald Duck: Goin' Quackers (2002)(GC)-map */ @@ -2253,26 +3135,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } -#if 0 - //todo too weird - /* Batman: Vengeance (2001)(PS2)-map */ - /* Disney's Tarzan: Untamed (2001)(PS2)-map */ - if (sb->version == 0x00000003 && sb->platform == UBI_PS2) { - config_sb_entry(sb, 0x30, 0x3c); - - config_sb_audio_fb(sb, 0x1c, (1 << 2), (1 << 3), (1 << 4)); /* not ok */ - config_sb_audio_hs(sb, 0x00, 0x24, 0x28, 0x28, 0x00, 0x00); - /* channels: 0? maybe 2=external, 1=internal? */ - /* stream type: always PS-ADPCM (interleave unknown) */ - /* sb->cfg.audio_stream_string = "STRM.SM1"; */ /* fixed */ - - config_sb_sequence(sb, 0x2c, 0x10); /* this is normal enough */ - - /* layers have a weird format too */ - return 1; - } -#endif - #if 0 //todo group flags and maybe num_samples for sfx are off /* Myst III: Exile (2001)(PS2)-map */ @@ -2281,7 +3143,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fb(sb, 0x1c, (1 << 3), (1 << 6), (1 << 4)); //??? config_sb_audio_hs(sb, 0x24, 0x28, 0x2c, 0x34, 0x44, 0x6c); - sb->cfg.audio_external_flag = 0x6c; /* no external flag? use codec as flag */ + sb->cfg.audio_streamed_flag = 0x6c; /* no streamed flag? use codec as flag */ config_sb_sequence(sb, 0x2c, 0x24); return 1; @@ -2326,14 +3188,14 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; /* both SC:PT's LMx and SMx have 33 maps, SC1 doesn't */ - is_sc2_ps2_gc = read_32bit(0x08, streamFile) == 0x21; + is_sc2_ps2_gc = read_32bit(0x08, sf) == 0x21; /* could also load ECHELON.SP1/Echelon.SP3 and test BE 0x04 == 0x00ACBF77, * but it's worse for localization subdirs without it */ } /* Splinter Cell (2002)(PS2)-map */ - /* Splinter Cell: Pandora Tomorrow (2006)(PS2)-map 0x00000007 */ + /* Splinter Cell: Pandora Tomorrow (2004)(PS2)-map */ if (sb->version == 0x00000007 && sb->platform == UBI_PS2) { config_sb_entry(sb, 0x40, 0x70); @@ -2368,12 +3230,12 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { if (is_sc2_ps2_gc) { sb->cfg.map_entry_size = 0x38; - sb->cfg.audio_external_and = 0x01000000; /* did somebody forget about BE? */ + sb->cfg.audio_streamed_and = 0x01000000; /* did somebody forget about BE? */ } return 1; } - /* Tom Clancy's Rainbow Six 3: Raven Shield + addons (2003)(PC)-bank 0x0000000B */ + /* Tom Clancy's Rainbow Six 3: Raven Shield + addons (2003)(PC)-bank */ if (sb->version == 0x0000000B && sb->platform == UBI_PC) { config_sb_entry(sb, 0x5c, 0x7c); @@ -2388,7 +3250,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - /* Prince of Persia: The Sands of Time Demo (2003)(Xbox)-bank 0x0000000D */ + /* Prince of Persia: The Sands of Time (Demo)(2003)(Xbox)-bank */ if (sb->version == 0x0000000D && sb->platform == UBI_XBOX) { config_sb_entry(sb, 0x5c, 0x74); @@ -2396,21 +3258,21 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_hs(sb, 0x46, 0x40, 0x2c, 0x34, 0x4c, 0x48); sb->cfg.audio_has_internal_names = 1; - config_sb_sequence(sb, 0x28, 0x34); + //config_sb_sequence(sb, 0x28, 0x34); config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30); config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); return 1; } - /* Prince of Persia: The Sands of Time Demo (2003)(Xbox)-bank 0x000A0000 */ + /* Prince of Persia: The Sands of Time (Demo)(2003)(Xbox)-bank */ if (sb->version == 0x000A0000 && sb->platform == UBI_XBOX) { config_sb_entry(sb, 0x64, 0x78); config_sb_audio_fs(sb, 0x24, 0x28, 0x2c); config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); - config_sb_sequence(sb, 0x28, 0x14); + //config_sb_sequence(sb, 0x28, 0x14); config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30); config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); @@ -2435,12 +3297,12 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { /* two configs with same id; use project file as identifier */ if (sb->version == 0x000A0007 && sb->platform == UBI_PS2) { - STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "BIAAUDIO.SP1"); - if (!streamTest) /* try again for localized subfolders */ - streamTest = open_streamfile_by_filename(streamFile, "../BIAAUDIO.SP1"); - if (streamTest) { + STREAMFILE* test_sf = open_streamfile_by_filename(sf, "BIAAUDIO.SP1"); + if (!test_sf) /* try again for localized subfolders */ + test_sf = open_streamfile_by_filename(sf, "../BIAAUDIO.SP1"); + if (test_sf) { is_bia_ps2 = 1; - close_streamfile(streamTest); + close_streamfile(test_sf); } } @@ -2448,7 +3310,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { /* Tom Clancy's Rainbow Six 3 (2003)(PS2)-bank 0x000A0007 */ /* Tom Clancy's Ghost Recon 2 (2004)(PS2)-bank 0x000A0007 */ /* Splinter Cell: Pandora Tomorrow (2006)(PS2)-bank 0x000A0008 (separate banks from main map) */ - /* Prince of Persia: Warrior Within Demo (2004)(PS2)-bank 0x00100000 */ + /* Prince of Persia: Warrior Within (Demo)(2004)(PS2)-bank 0x00100000 */ /* Prince of Persia: Warrior Within (2004)(PS2)-bank 0x00120009 */ if ((sb->version == 0x000A0002 && sb->platform == UBI_PS2) || (sb->version == 0x000A0004 && sb->platform == UBI_PS2) || @@ -2471,8 +3333,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - /* Brothers in Arms: Road to Hill 30 (2005)[PS2] */ - /* Brothers in Arms: Earned in Blood (2005)[PS2] */ + /* Brothers in Arms: Road to Hill 30 (2005)(PS2)-bank */ + /* Brothers in Arms: Earned in Blood (2005)(PS2)-bank */ if (sb->version == 0x000A0007 && sb->platform == UBI_PS2 && is_bia_ps2) { config_sb_entry(sb, 0x5c, 0x14c); @@ -2492,14 +3354,14 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - /* Batman: Rise of Sin Tzu (2003)(Xbox)-map 0x000A0003 */ + /* Batman: Rise of Sin Tzu (2003)(Xbox)-map */ if (sb->version == 0x000A0003 && sb->platform == UBI_XBOX) { config_sb_entry(sb, 0x64, 0x80); config_sb_audio_fs(sb, 0x24, 0x28, 0x34); config_sb_audio_hs(sb, 0x52, 0x4c, 0x38, 0x40, 0x58, 0x54); sb->cfg.audio_has_internal_names = 1; - sb->cfg.default_codec_for_group0 = 1; + sb->cfg.default_codec_for_subblock0 = 1; config_sb_sequence(sb, 0x28, 0x14); @@ -2517,7 +3379,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x28, 0x2c); config_sb_audio_hs(sb, 0x4a, 0x44, 0x30, 0x38, 0x50, 0x4c); sb->cfg.audio_has_internal_names = 1; - sb->cfg.default_codec_for_group0 = 1; + sb->cfg.default_codec_for_subblock0 = 1; config_sb_sequence(sb, 0x28, 0x14); @@ -2546,14 +3408,14 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - /* Tom Clancy's Rainbow Six 3 (2003)(Xbox)-bank 0x000A0007 */ + /* Tom Clancy's Rainbow Six 3 (2003)(Xbox)-bank */ if (sb->version == 0x000A0007 && sb->platform == UBI_XBOX) { config_sb_entry(sb, 0x64, 0x8c); config_sb_audio_fs(sb, 0x24, 0x28, 0x40); config_sb_audio_hs(sb, 0x5e, 0x58, 0x44, 0x4c, 0x64, 0x60); sb->cfg.audio_has_internal_names = 1; - sb->cfg.default_codec_for_group0 = 1; + sb->cfg.default_codec_for_subblock0 = 1; config_sb_sequence(sb, 0x28, 0x14); @@ -2564,7 +3426,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - /* Myst IV Demo (2004)(PC)-bank */ + /* Myst IV (Demo)(2004)(PC)-bank */ if (sb->version == 0x00100000 && sb->platform == UBI_PC) { config_sb_entry(sb, 0x68, 0xa4); @@ -2574,7 +3436,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - /* Prince of Persia: Warrior Within Demo (2004)(PC)-bank 0x00120006 */ + /* Prince of Persia: Warrior Within (Demo)(2004)(PC)-bank 0x00120006 */ /* Prince of Persia: Warrior Within (2004)(PC)-bank 0x00120009 */ if ((sb->version == 0x00120006 && sb->platform == UBI_PC) || (sb->version == 0x00120009 && sb->platform == UBI_PC)) { @@ -2595,7 +3457,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_fs(sb, 0x24, 0x28, 0x40); config_sb_audio_hs(sb, 0x60, 0x58, 0x44, 0x4c, 0x68, 0x64); sb->cfg.audio_has_internal_names = 1; - sb->cfg.default_codec_for_group0 = 1; + sb->cfg.default_codec_for_subblock0 = 1; config_sb_sequence(sb, 0x28, 0x14); return 1; @@ -2614,10 +3476,10 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { /* two configs with same id and both sb4/sm4; use project file as identifier */ if (sb->version == 0x0012000C && sb->platform == UBI_PSP) { - STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "BIAAUDIO.SP4"); - if (streamTest) { + STREAMFILE* test_sf = open_streamfile_by_filename(sf, "BIAAUDIO.SP4"); + if (test_sf) { is_biadd_psp = 1; - close_streamfile(streamTest); + close_streamfile(test_sf); } } @@ -2830,7 +3692,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - /* Open Season (2006)(PC)-map 0x00180003 */ + /* Open Season (2006)(PC)-map */ if (sb->version == 0x00180003 && sb->platform == UBI_PC) { config_sb_entry(sb, 0x68, 0x78); @@ -2846,7 +3708,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - /* Open Season (2006)(Xbox)-map 0x00180003 */ + /* Open Season (2006)(Xbox)-map */ if (sb->version == 0x00180003 && sb->platform == UBI_XBOX) { config_sb_entry(sb, 0x48, 0x58); @@ -2862,7 +3724,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - /* Open Season (2006)(GC)-map 0x00180003 */ + /* Open Season (2006)(GC)-map */ if (sb->version == 0x00180003 && sb->platform == UBI_GC) { config_sb_entry(sb, 0x68, 0x6c); @@ -2881,10 +3743,10 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { /* two configs with same id; use project file as identifier */ if (sb->version == 0x00180006 && sb->platform == UBI_PC) { - STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "Sc4_online_SoundProject.SP0"); - if (streamTest) { + STREAMFILE* test_sf = open_streamfile_by_filename(sf, "Sc4_online_SoundProject.SP0"); + if (test_sf) { is_sc4_pc_online = 1; - close_streamfile(streamTest); + close_streamfile(test_sf); } } @@ -2943,13 +3805,26 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } + /* Tom Clancy's Ghost Recon Advanced Warfighter 2 (2007)(X360)-bank */ + if (sb->version == 0x0018000b && sb->platform == UBI_X360) { + config_sb_entry(sb, 0x68, 0x70); + + config_sb_audio_fs(sb, 0x28, 0x2c, 0x30); + config_sb_audio_he(sb, 0x3c, 0x40, 0x48, 0x50, 0x58, 0x5c); + sb->cfg.audio_xma_offset = 0x68; + + config_sb_sequence(sb, 0x2c, 0x14); + + return 1; + } + /* TMNT (2007)(PSP)-map 0x00190001 */ /* Surf's Up (2007)(PSP)-map 0x00190005 */ if ((sb->version == 0x00190001 && sb->platform == UBI_PSP) || (sb->version == 0x00190005 && sb->platform == UBI_PSP)) { config_sb_entry(sb, 0x48, 0x58); - config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4)); /* assumed group_flag */ + config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4)); /* assumed subblock_flag */ config_sb_audio_he(sb, 0x28, 0x2c, 0x34, 0x3c, 0x44, 0x48); config_sb_sequence(sb, 0x2c, 0x10); @@ -2979,7 +3854,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { if (sb->version == 0x00190002 && sb->platform == UBI_PS2) { config_sb_entry(sb, 0x48, 0x5c); - config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4)); /* assumed group_flag */ + config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 4)); /* assumed subblock_flag */ config_sb_audio_he(sb, 0x28, 0x2c, 0x34, 0x3c, 0x44, 0x48); config_sb_sequence(sb, 0x2c, 0x10); @@ -3039,8 +3914,20 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } - /* Cranium Kabookii (2007)(Wii)-bank 0x001a0003 */ - if (sb->version == 0x001a0003 && sb->platform == UBI_WII) { + /* Tom Clancy's Ghost Recon Advanced Warfighter 2 (2007)(PS3)-bank */ + if (sb->version == 0x001A0003 && sb->platform == UBI_PS3) { + config_sb_entry(sb, 0x6c, 0x78); + + config_sb_audio_fs(sb, 0x30, 0x34, 0x38); + config_sb_audio_he(sb, 0x40, 0x44, 0x4c, 0x54, 0x5c, 0x60); + + config_sb_sequence(sb, 0x2c, 0x14); + + return 1; + } + + /* Cranium Kabookii (2007)(Wii)-bank */ + if (sb->version == 0x001A0003 && sb->platform == UBI_WII) { config_sb_entry(sb, 0x6c, 0x78); config_sb_audio_fs(sb, 0x2c, 0x30, 0x34); @@ -3072,7 +3959,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { if (sb->version == 0x001D0000 && sb->platform == UBI_PSP) { config_sb_entry(sb, 0x40, 0x60); - config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 5)); /* assumed group_flag */ + config_sb_audio_fb(sb, 0x20, (1 << 2), (1 << 3), (1 << 5)); /* assumed subblock_flag */ config_sb_audio_he(sb, 0x28, 0x30, 0x38, 0x40, 0x48, 0x4c); return 1; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xvag.c b/Frameworks/vgmstream/vgmstream/src/meta/xvag.c index 7bf942902..a0e6f3707 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/xvag.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/xvag.c @@ -24,37 +24,37 @@ typedef struct { off_t stream_offset; } xvag_header; -static int init_xvag_atrac9(STREAMFILE *streamFile, VGMSTREAM* vgmstream, xvag_header * xvag, off_t chunk_offset); -static layered_layout_data* build_layered_xvag(STREAMFILE *streamFile, xvag_header * xvag, off_t chunk_offset, off_t start_offset); +static int init_xvag_atrac9(STREAMFILE* sf, VGMSTREAM* vgmstream, xvag_header * xvag, off_t chunk_offset); +static layered_layout_data* build_layered_xvag(STREAMFILE* sf, xvag_header * xvag, off_t chunk_offset, off_t start_offset); /* XVAG - Sony's Scream Tool/Stream Creator format (God of War III, Ratchet & Clank Future, The Last of Us, Uncharted) */ -VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE* temp_streamFile = NULL; +VGMSTREAM* init_vgmstream_xvag(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE* temp_sf = NULL; xvag_header xvag = {0}; int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; off_t start_offset, chunk_offset, first_offset = 0x20; size_t chunk_size; - int total_subsongs = 0, target_subsong = streamFile->stream_index; + int total_subsongs = 0, target_subsong = sf->stream_index; /* checks */ /* .xvag: standard * (extensionless): The Last Of Us (PS3) speech files */ - if (!check_extensions(streamFile,"xvag,")) + if (!check_extensions(sf,"xvag,")) goto fail; - if (read_32bitBE(0x00,streamFile) != 0x58564147) /* "XVAG" */ + if (read_32bitBE(0x00,sf) != 0x58564147) /* "XVAG" */ goto fail; /* endian flag (XVAGs of the same game can use BE or LE, usually when reusing from other platforms) */ - xvag.big_endian = read_8bit(0x08,streamFile) & 0x01; + xvag.big_endian = read_8bit(0x08,sf) & 0x01; if (xvag.big_endian) { read_32bit = read_32bitBE; } else { read_32bit = read_32bitLE; } - start_offset = read_32bit(0x04,streamFile); + start_offset = read_32bit(0x04,sf); /* 0x08: flags? (&0x01=big endian, 0x02=?, 0x06=full RIFF AT9?) * 0x09: flags2? (0x00/0x01/0x04, speaker mode?) * 0x0a: always 0? @@ -62,24 +62,24 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { /* "fmat": base format (always first) */ - if (!find_chunk(streamFile, 0x666D6174,first_offset,0, &chunk_offset,&chunk_size, xvag.big_endian, 1)) /*"fmat"*/ + if (!find_chunk(sf, 0x666D6174,first_offset,0, &chunk_offset,&chunk_size, xvag.big_endian, 1)) /*"fmat"*/ goto fail; - xvag.channels = read_32bit(chunk_offset+0x00,streamFile); - xvag.codec = read_32bit(chunk_offset+0x04,streamFile); - xvag.num_samples = read_32bit(chunk_offset+0x08,streamFile); + xvag.channels = read_32bit(chunk_offset+0x00,sf); + xvag.codec = read_32bit(chunk_offset+0x04,sf); + xvag.num_samples = read_32bit(chunk_offset+0x08,sf); /* 0x0c: samples again? */ - VGM_ASSERT(xvag.num_samples != read_32bit(chunk_offset+0x0c,streamFile), "XVAG: num_samples values don't match\n"); + VGM_ASSERT(xvag.num_samples != read_32bit(chunk_offset+0x0c,sf), "XVAG: num_samples values don't match\n"); - xvag.factor = read_32bit(chunk_offset+0x10,streamFile); /* for interleave */ - xvag.sample_rate = read_32bit(chunk_offset+0x14,streamFile); - xvag.data_size = read_32bit(chunk_offset+0x18,streamFile); /* not always accurate */ + xvag.factor = read_32bit(chunk_offset+0x10,sf); /* for interleave */ + xvag.sample_rate = read_32bit(chunk_offset+0x14,sf); + xvag.data_size = read_32bit(chunk_offset+0x18,sf); /* not always accurate */ /* extra data, seen in versions 0x61+ */ if (chunk_size > 0x1c) { /* number of interleaved subsongs */ - xvag.subsongs = read_32bit(chunk_offset+0x1c,streamFile); + xvag.subsongs = read_32bit(chunk_offset+0x1c,sf); /* number of interleaved layers (layers * channels_per_layer = channels) */ - xvag.layers = read_32bit(chunk_offset+0x20,streamFile); + xvag.layers = read_32bit(chunk_offset+0x20,sf); } else { xvag.subsongs = 1; @@ -99,9 +99,9 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { /* XVAG has no looping, but some PS3 PS-ADPCM seems to do full loops (without data flags) */ if (xvag.codec == 0x06 && xvag.subsongs == 1) { - size_t file_size = get_streamfile_size(streamFile); + size_t file_size = get_streamfile_size(sf); /* simply test if last frame is not empty = may loop */ - xvag.loop_flag = (read_8bit(file_size - 0x01, streamFile) != 0); + xvag.loop_flag = (read_8bit(file_size - 0x01, sf) != 0); xvag.loop_start = 0; xvag.loop_end = ps_bytes_to_samples(file_size - start_offset, xvag.channels); } @@ -162,7 +162,7 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { if (xvag.layers > 1 && !(xvag.layers*1 == vgmstream->channels || xvag.layers*2 == vgmstream->channels)) goto fail; /* "mpin": mpeg info */ - if (!find_chunk(streamFile, 0x6D70696E,first_offset,0, &chunk_offset,NULL, xvag.big_endian, 1)) /*"mpin"*/ + if (!find_chunk(sf, 0x6D70696E,first_offset,0, &chunk_offset,NULL, xvag.big_endian, 1)) /*"mpin"*/ goto fail; /* all layers/subsongs share the same config; not very useful but for posterity: @@ -182,18 +182,18 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { * - 0x34: data size * (rest is padding) * */ - cfg.chunk_size = read_32bit(chunk_offset+0x1c,streamFile); - cfg.skip_samples = read_32bit(chunk_offset+0x20,streamFile); + cfg.chunk_size = read_32bit(chunk_offset+0x1c,sf); + cfg.skip_samples = read_32bit(chunk_offset+0x20,sf); cfg.interleave = cfg.chunk_size * xvag.factor; - vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_XVAG, &cfg); + vgmstream->codec_data = init_mpeg_custom(sf, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_XVAG, &cfg); if (!vgmstream->codec_data) goto fail; vgmstream->layout_type = layout_none; /* interleaved subsongs, rarely [Sly Cooper: Thieves in Time (PS3)] */ if (xvag.subsongs > 1) { - temp_streamFile = setup_xvag_streamfile(streamFile, start_offset, cfg.interleave,cfg.chunk_size, (target_subsong-1), total_subsongs); - if (!temp_streamFile) goto fail; + temp_sf = setup_xvag_streamfile(sf, start_offset, cfg.interleave,cfg.chunk_size, (target_subsong-1), total_subsongs); + if (!temp_sf) goto fail; start_offset = 0; } @@ -207,13 +207,13 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { /* "a9in": ATRAC9 info */ /* 0x00: frame size, 0x04: samples per frame, 0x0c: fact num_samples (no change), 0x10: encoder delay1 */ - if (!find_chunk(streamFile, 0x6139696E,first_offset,0, &chunk_offset,NULL, xvag.big_endian, 1)) /*"a9in"*/ + if (!find_chunk(sf, 0x6139696E,first_offset,0, &chunk_offset,NULL, xvag.big_endian, 1)) /*"a9in"*/ goto fail; if (xvag.layers > 1) { /* some Vita/PS4 multichannel [flower (Vita), Uncharted Collection (PS4)]. PS4 ATRAC9 also * does single-stream >2ch, but this can do configs ATRAC9 can't, like 5ch/14ch/etc */ - vgmstream->layout_data = build_layered_xvag(streamFile, &xvag, chunk_offset, start_offset); + vgmstream->layout_data = build_layered_xvag(sf, &xvag, chunk_offset, start_offset); if (!vgmstream->layout_data) goto fail; vgmstream->coding_type = coding_ATRAC9; vgmstream->layout_type = layout_layered; @@ -222,12 +222,12 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { } else { /* interleaved subsongs (section layers) */ - size_t frame_size = read_32bit(chunk_offset+0x00,streamFile); + size_t frame_size = read_32bit(chunk_offset+0x00,sf); - if (!init_xvag_atrac9(streamFile, vgmstream, &xvag, chunk_offset)) + if (!init_xvag_atrac9(sf, vgmstream, &xvag, chunk_offset)) goto fail; - temp_streamFile = setup_xvag_streamfile(streamFile, start_offset, frame_size*xvag.factor,frame_size, (target_subsong-1), total_subsongs); - if (!temp_streamFile) goto fail; + temp_sf = setup_xvag_streamfile(sf, start_offset, frame_size*xvag.factor,frame_size, (target_subsong-1), total_subsongs); + if (!temp_sf) goto fail; start_offset = 0; } @@ -240,25 +240,25 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { } - if (!vgmstream_open_stream(vgmstream,temp_streamFile ? temp_streamFile : streamFile,start_offset)) + if (!vgmstream_open_stream(vgmstream,temp_sf ? temp_sf : sf,start_offset)) goto fail; - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); return vgmstream; fail: - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); close_vgmstream(vgmstream); return NULL; } #ifdef VGM_USE_ATRAC9 -static int init_xvag_atrac9(STREAMFILE *streamFile, VGMSTREAM* vgmstream, xvag_header * xvag, off_t chunk_offset) { +static int init_xvag_atrac9(STREAMFILE* sf, VGMSTREAM* vgmstream, xvag_header * xvag, off_t chunk_offset) { int32_t (*read_32bit)(off_t,STREAMFILE*) = xvag->big_endian ? read_32bitBE : read_32bitLE; atrac9_config cfg = {0}; cfg.channels = vgmstream->channels; - cfg.config_data = read_32bitBE(chunk_offset+0x08,streamFile); - cfg.encoder_delay = read_32bit(chunk_offset+0x14,streamFile); + cfg.config_data = read_32bitBE(chunk_offset+0x08,sf); + cfg.encoder_delay = read_32bit(chunk_offset+0x14,sf); vgmstream->codec_data = init_atrac9(&cfg); if (!vgmstream->codec_data) goto fail; @@ -271,9 +271,9 @@ fail: } #endif -static layered_layout_data* build_layered_xvag(STREAMFILE *streamFile, xvag_header * xvag, off_t chunk_offset, off_t start_offset) { +static layered_layout_data* build_layered_xvag(STREAMFILE* sf, xvag_header * xvag, off_t chunk_offset, off_t start_offset) { layered_layout_data* data = NULL; - STREAMFILE* temp_streamFile = NULL; + STREAMFILE* temp_sf = NULL; int32_t (*read_32bit)(off_t,STREAMFILE*) = xvag->big_endian ? read_32bitBE : read_32bitLE; int i, layers = xvag->layers; @@ -296,12 +296,12 @@ static layered_layout_data* build_layered_xvag(STREAMFILE *streamFile, xvag_head switch(xvag->codec) { #ifdef VGM_USE_ATRAC9 case 0x09: { - size_t frame_size = read_32bit(chunk_offset+0x00,streamFile); + size_t frame_size = read_32bit(chunk_offset+0x00,sf); - if (!init_xvag_atrac9(streamFile, data->layers[i], xvag, chunk_offset)) + if (!init_xvag_atrac9(sf, data->layers[i], xvag, chunk_offset)) goto fail; - temp_streamFile = setup_xvag_streamfile(streamFile, start_offset, frame_size*xvag->factor,frame_size, i, layers); - if (!temp_streamFile) goto fail; + temp_sf = setup_xvag_streamfile(sf, start_offset, frame_size*xvag->factor,frame_size, i, layers); + if (!temp_sf) goto fail; break; } #endif @@ -309,9 +309,11 @@ static layered_layout_data* build_layered_xvag(STREAMFILE *streamFile, xvag_head goto fail; } - if ( !vgmstream_open_stream(data->layers[i], temp_streamFile, 0x00) ) + if (!vgmstream_open_stream(data->layers[i], temp_sf, 0x00)) goto fail; - close_streamfile(temp_streamFile); + + close_streamfile(temp_sf); + temp_sf = NULL; } /* setup layered VGMSTREAMs */ @@ -320,7 +322,7 @@ static layered_layout_data* build_layered_xvag(STREAMFILE *streamFile, xvag_head return data; fail: - close_streamfile(temp_streamFile); + close_streamfile(temp_sf); free_layout_layered(data); return NULL; } diff --git a/Frameworks/vgmstream/vgmstream/src/streamfile.c b/Frameworks/vgmstream/vgmstream/src/streamfile.c index 6ce0626a0..47688922e 100644 --- a/Frameworks/vgmstream/vgmstream/src/streamfile.c +++ b/Frameworks/vgmstream/vgmstream/src/streamfile.c @@ -680,9 +680,12 @@ STREAMFILE* open_fakename_streamfile(STREAMFILE *streamfile, const char *fakenam } if (fakeext) { - char * ext = strrchr(this_sf->fakename,'.'); - if (ext != NULL) + char* ext = strrchr(this_sf->fakename,'.'); + if (ext != NULL) { ext[1] = '\0'; /* truncate past dot */ + } else { + strcat(this_sf->fakename, "."); /* no extension = add dot */ + } strcat(this_sf->fakename, fakeext); } @@ -1187,6 +1190,9 @@ static int find_chunk_internal(STREAMFILE *streamFile, uint32_t chunk_id, off_t uint32_t chunk_size = read_32bit_size(offset + 0x04,streamFile); //;VGM_LOG("CHUNK: type=%x, size=%x at %lx\n", chunk_type, chunk_size, offset); + if (chunk_type == 0xFFFFFFFF || chunk_size == 0xFFFFFFFF) + return 0; + if (chunk_type == chunk_id) { if (out_chunk_offset) *out_chunk_offset = offset + 0x08; if (out_chunk_size) *out_chunk_size = chunk_size; diff --git a/Frameworks/vgmstream/vgmstream/src/util.c b/Frameworks/vgmstream/vgmstream/src/util.c index 95b58a4f9..3c7ccc3e6 100644 --- a/Frameworks/vgmstream/vgmstream/src/util.c +++ b/Frameworks/vgmstream/vgmstream/src/util.c @@ -61,8 +61,6 @@ void interleave_stereo(sample_t * buffer, int32_t sample_count) { else belongs = (tomove-sample_count)*2+1; - printf("move %d to %d\n",tomove,belongs); - temp = buffer[belongs]; buffer[belongs] = moving; moving = temp; @@ -100,6 +98,14 @@ void put_32bitBE(uint8_t * buf, int32_t i) { buf[3] = (uint8_t)(i & 0xFF); } +int round10(int val) { + int round_val = val % 10; + if (round_val < 5) /* half-down rounding */ + return val - round_val; + else + return val + (10 - round_val); +} + void swap_samples_le(sample_t *buf, int count) { /* Windows can't be BE... I think */ #if !defined(_WIN32) diff --git a/Frameworks/vgmstream/vgmstream/src/util.h b/Frameworks/vgmstream/vgmstream/src/util.h index 98c97100c..21a98d89b 100644 --- a/Frameworks/vgmstream/vgmstream/src/util.h +++ b/Frameworks/vgmstream/vgmstream/src/util.h @@ -7,6 +7,8 @@ #ifndef _UTIL_H #define _UTIL_H +/* very common functions, so static inline in .h is useful to avoid some call overhead */ + /* host endian independent multi-byte integer reading */ static inline int16_t get_16bitBE(uint8_t * p) { @@ -91,13 +93,9 @@ static inline int clamp16(int32_t val) { else return val; } -static inline int round10(int val) { - int round_val = val % 10; - if (round_val < 5) /* half-down rounding */ - return val - round_val; - else - return val + (10 - round_val); -} +/* less common functions, no need to inline */ + +int round10(int val); /* return a file's extension (a pointer to the first character of the * extension in the original filename or the ending null byte if no extension */ diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index 4960b7803..c8662148c 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -351,6 +351,9 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_ubi_sb, init_vgmstream_ubi_sm, init_vgmstream_ubi_bnm, + init_vgmstream_ubi_bnm_ps2, + init_vgmstream_ubi_dat, + init_vgmstream_ubi_blk, init_vgmstream_ezw, init_vgmstream_vxn, init_vgmstream_ea_snr_sns, @@ -494,6 +497,10 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_bkhd_fx, init_vgmstream_diva, init_vgmstream_imuse, + init_vgmstream_ktsr, + init_vgmstream_mups, + init_vgmstream_kat, + init_vgmstream_pcm_success, /* 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 */ diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.h b/Frameworks/vgmstream/vgmstream/src/vgmstream.h index bc2be360a..0f8fed8ef 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h @@ -317,7 +317,6 @@ typedef enum { meta_DSP_WII_WSD, /* Phantom Brave (WII) */ meta_WII_NDP, /* Vertigo (Wii) */ meta_DSP_YGO, /* Konami: Yu-Gi-Oh! The Falsebound Kingdom (NGC), Hikaru no Go 3 (NGC) */ - meta_DSP_SADF, /* Procyon Studio SADF - Xenoblade Chronicles 2 (Switch) */ meta_STRM, /* Nintendo STRM */ meta_RSTM, /* Nintendo RSTM (Revolution Stream, similar to STRM) */ @@ -430,7 +429,7 @@ typedef enum { meta_NGC_SSM, /* Golden Gashbell Full Power */ meta_PS2_JOE, /* Wall-E / Pixar games */ meta_NGC_YMF, /* WWE WrestleMania X8 */ - meta_SADL, /* .sad */ + meta_SADL, meta_PS2_CCC, /* Tokyo Xtreme Racer DRIFT 2 */ meta_FAG, /* Jackie Chan - Stuntmaster */ meta_PS2_MIHB, /* Merged MIH+MIB */ @@ -691,6 +690,7 @@ typedef enum { meta_WV2, /* Slave Zero (PC) */ meta_XAU_KONAMI, /* Yu-Gi-Oh - The Dawn of Destiny (Xbox) */ meta_DERF, /* Stupid Invaders (PC) */ + meta_SADF, meta_UTK, meta_NXA, meta_ADPCM_CAPCOM, @@ -739,6 +739,9 @@ typedef enum { meta_WWISE_FX, meta_DIVA, meta_IMUSE, + meta_KTSR, + meta_KAT, + meta_PCM_SUCCESS, } meta_t; /* standard WAVEFORMATEXTENSIBLE speaker positions */