diff --git a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj index 06258c4ef..52afce74b 100644 --- a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj +++ b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj @@ -133,6 +133,7 @@ 832BF82D21E0514B006F50F1 /* nus3audio.c in Sources */ = {isa = PBXBuildFile; fileRef = 832BF81B21E0514B006F50F1 /* nus3audio.c */; }; 832C70BF1E9335E400BD7B4E /* Vorbis.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 83F4128F1E932F9A002E37D0 /* Vorbis.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 83345A521F8AEB2800B2EAA4 /* xvag.c in Sources */ = {isa = PBXBuildFile; fileRef = 83345A4E1F8AEB2800B2EAA4 /* xvag.c */; }; + 83349719275DD2AC00302E21 /* wbk.c in Sources */ = {isa = PBXBuildFile; fileRef = 83349715275DD2AC00302E21 /* wbk.c */; }; 833A7A2E1ED11961003EC53E /* xau.c in Sources */ = {isa = PBXBuildFile; fileRef = 833A7A2D1ED11961003EC53E /* xau.c */; }; 8342469420C4D23000926E48 /* h4m.c in Sources */ = {isa = PBXBuildFile; fileRef = 8342469020C4D22F00926E48 /* h4m.c */; }; 8342469620C4D23D00926E48 /* blocked_h4m.c in Sources */ = {isa = PBXBuildFile; fileRef = 8342469520C4D23D00926E48 /* blocked_h4m.c */; }; @@ -948,6 +949,7 @@ 832BF81A21E0514A006F50F1 /* hca_keys_awb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hca_keys_awb.h; sourceTree = ""; }; 832BF81B21E0514B006F50F1 /* nus3audio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = nus3audio.c; sourceTree = ""; }; 83345A4E1F8AEB2800B2EAA4 /* xvag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xvag.c; sourceTree = ""; }; + 83349715275DD2AC00302E21 /* wbk.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = wbk.c; sourceTree = ""; }; 833A7A2D1ED11961003EC53E /* xau.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xau.c; sourceTree = ""; }; 8342469020C4D22F00926E48 /* h4m.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = h4m.c; sourceTree = ""; }; 8342469520C4D23D00926E48 /* blocked_h4m.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = blocked_h4m.c; sourceTree = ""; }; @@ -2205,6 +2207,7 @@ 8306B0D02098458F000302D4 /* wave_segmented.c */, 8306B0C92098458E000302D4 /* wave.c */, 834FE0D0215C79E8000A5D3D /* wavebatch.c */, + 83349715275DD2AC00302E21 /* wbk.c */, 836F6F0018BDC2190095E648 /* wii_bns.c */, 836F6F0118BDC2190095E648 /* wii_mus.c */, 836F6F0218BDC2190095E648 /* wii_ras.c */, @@ -2671,6 +2674,7 @@ 8346D97C25BF838C00D1A8B0 /* mjb_mjh.c in Sources */, 839C3D27270D49FF00E13653 /* lpcm_fb.c in Sources */, 8373341823F60C7B00DE14DC /* tgcadpcm_decoder.c in Sources */, + 83349719275DD2AC00302E21 /* wbk.c in Sources */, 83FC417326D3304D009A2022 /* xsh_xsd_xss.c in Sources */, 836F6F3318BDC2190095E648 /* ngc_dtk_decoder.c in Sources */, 83E7FD6525EF2B2400683FD2 /* tac.c in Sources */, diff --git a/Frameworks/vgmstream/vgmstream/src/formats.c b/Frameworks/vgmstream/vgmstream/src/formats.c index d22e9654a..e604de758 100644 --- a/Frameworks/vgmstream/vgmstream/src/formats.c +++ b/Frameworks/vgmstream/vgmstream/src/formats.c @@ -592,6 +592,7 @@ static const char* extension_list[] = { "wb", "wb2", "wbd", + "wbk", "wd", "wem", "wii", @@ -1381,6 +1382,8 @@ static const meta_info meta_info_list[] = { {meta_PSB, "M2 PSB header"}, {meta_LOPU_FB, "French-Bread LOPU header"}, {meta_LPCM_FB, "French-Bread LPCM header"}, + {meta_WBK, "Treyarch WBK header"}, + {meta_DSP_APEX, "Koei Tecmo APEX header"}, }; void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) { diff --git a/Frameworks/vgmstream/vgmstream/src/meta/atsl.c b/Frameworks/vgmstream/vgmstream/src/meta/atsl.c index c1f272935..86869b9a1 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/atsl.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/atsl.c @@ -36,16 +36,18 @@ VGMSTREAM* init_vgmstream_atsl(STREAMFILE* sf) { * - 00000201 00020001 .atsl3 from Fist of North Star: Ken's Rage 2 (PS3)[ATRAC3] * 00000301 00020101 (same) * - 01040301 00060301 .atsl4 from Nobunaga's Ambition: Sphere of Influence (PS4)[ATRAC9] - * - 00060301 00040301 atsl in G1L from One Piece Pirate Warriors 3 (Vita)[ATRAC9] - * - 00060301 00010301 atsl in G1L from One Piece Pirate Warriors 3 (PC)[KOVS] - * - 000A0301 00010501 atsl in G1L from Warriors All-Stars (PC)[KOVS] - * - 000B0301 00080601 atsl in G1l from Sengoku Musou Sanada Maru (Switch)[KTSS] + * - 00060301 00040301 .atsl in G1L from One Piece Pirate Warriors 3 (Vita)[ATRAC9] + * - 00060301 00010301 .atsl in G1L from One Piece Pirate Warriors 3 (PC)[KOVS] + * - 000A0301 00010501 .atsl in G1L from Warriors All-Stars (PC)[KOVS] 2017-09 + * - 01000000 01010501 .atsl from Nioh (PC)[KOVS] 2017-11 + * - 01000000 00010501 .atsl from Nioh (PC)[KOVS] 2017-11 + * - 000B0301 00080601 .atsl in G1l from Sengoku Musou Sanada Maru (Switch)[KTSS] 2017-09 * - 010C0301 01060601 .atsl from Dynasty Warriors 9 (PS4)[KTAC] - * - 01000000 01010501 .atsl from Nioh (PC)[KOVS] - * - 01000000 00010501 .atsl from Nioh (PC)[KOVS] + * - 010D0301 01010601 .atsl from Dynasty Warriors 9 DLC (PC)[KOVS] */ type = read_u16le(0x0c, sf); + //version = read_u16le(0x0e, sf); switch(type) { case 0x0100: /* KOVS */ init_vgmstream = init_vgmstream_ogg_vorbis; @@ -95,11 +97,16 @@ VGMSTREAM* init_vgmstream_atsl(STREAMFILE* sf) { /* parse entry header (in machine endianness) */ for (i = 0; i < entries; i++) { int is_unique = 1; + uint32_t entry_subfile_offset, entry_subfile_size; /* 0x00: id */ - uint32_t entry_subfile_offset = read_u32(header_size + i*entry_size + 0x04,sf); - uint32_t entry_subfile_size = read_u32(header_size + i*entry_size + 0x08,sf); - /* 0x08+: channels/sample rate/num_samples/loop_start/etc (match subfile header) */ + entry_subfile_offset = read_u32(header_size + i*entry_size + 0x04,sf); + entry_subfile_size = read_u32(header_size + i*entry_size + 0x08,sf); + /* 0x10+: config, channels/sample rate/num_samples/loop_start/channel layout/etc (matches subfile header) */ + + /* dummy entry, seen in DW9 DLC (has unique config though) */ + if (!entry_subfile_offset && !entry_subfile_size) + continue; /* check if current entry was repeated in a prev entry */ for (j = 0; j < i; j++) { diff --git a/Frameworks/vgmstream/vgmstream/src/meta/kwb.c b/Frameworks/vgmstream/vgmstream/src/meta/kwb.c index 1d922564d..950de9a2a 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/kwb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/kwb.c @@ -1,7 +1,7 @@ #include "meta.h" #include "../coding/coding.h" -typedef enum { PCM16, MSADPCM, DSP_HEAD, DSP_BODY, AT9, MSF, XMA2 } kwb_codec; +typedef enum { PCM16, MSADPCM, DSP_HEAD, DSP_BODY, AT9, MSF_APEX, XMA2 } kwb_codec; typedef struct { int big_endian; @@ -18,8 +18,8 @@ typedef struct { int loop_flag; int block_size; - off_t stream_offset; - size_t stream_size; + uint32_t stream_offset; + uint32_t stream_size; off_t dsp_offset; //off_t name_offset; @@ -121,20 +121,38 @@ static VGMSTREAM* init_vgmstream_koei_wavebank(kwb_header* kwb, STREAMFILE* sf_h read_s32 = kwb->big_endian ? read_s32be : read_s32le; /* container */ - if (kwb->codec == MSF) { + if (kwb->codec == MSF_APEX) { if (kwb->stream_offset == 0) { vgmstream = init_vgmstream_silence(0,0,0); /* dummy, whatevs */ if (!vgmstream) goto fail; } else { STREAMFILE* temp_sf = NULL; + init_vgmstream_t init_vgmstream = NULL; + const char* fake_ext; + uint32_t id; + + + id = read_u32be(kwb->stream_offset, sf_h); + if ((id & 0xFFFFFF00) == get_id32be("MSF\0")) { /* PS3 */ + kwb->stream_size = read_u32(kwb->stream_offset + 0x0c, sf_h) + 0x40; + fake_ext = "msf"; + init_vgmstream = init_vgmstream_msf; + } + else if (id == get_id32be("APEX")) { /* WiiU */ + kwb->stream_size = read_u32(kwb->stream_offset + 0x04, sf_h); /* not padded */ + fake_ext = "dsp"; + init_vgmstream = init_vgmstream_dsp_apex; + } + else { + vgm_logi("KWB: unknown type %x at %x\n", id, kwb->stream_offset); + goto fail; + } - kwb->stream_size = read_u32(kwb->stream_offset + 0x0c, sf_h) + 0x40; - - temp_sf = setup_subfile_streamfile(sf_h, kwb->stream_offset, kwb->stream_size, "msf"); + temp_sf = setup_subfile_streamfile(sf_h, kwb->stream_offset, kwb->stream_size, fake_ext); if (!temp_sf) goto fail; - vgmstream = init_vgmstream_msf(temp_sf); + vgmstream = init_vgmstream(temp_sf); close_streamfile(temp_sf); if (!vgmstream) goto fail; } @@ -641,9 +659,9 @@ static int parse_type_msfbank(kwb_header* kwb, off_t offset, STREAMFILE* sf) { relative_subsong = kwb->target_subsong - current_subsongs; header_offset = offset + 0x30 + (relative_subsong-1) * 0x04; - /* just a dumb table pointing to MSF, entries can be dummy */ + /* just a dumb table pointing to MSF/APEX, entries can be dummy */ kwb->stream_offset = read_u32be(header_offset, sf); - kwb->codec = MSF; + kwb->codec = MSF_APEX; kwb->stream_offset += offset; @@ -728,7 +746,7 @@ static int parse_type_xwsfile(kwb_header* kwb, off_t offset, STREAMFILE* sf) { goto fail; } else { - VGM_LOG("XWS: unknown type %x at head=%x, body=%x\n", entry_type, head_offset, body_offset); + vgm_logi("XWS: unknown type %x at head=%x, body=%x\n", entry_type, head_offset, body_offset); goto fail; } } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/meta.h b/Frameworks/vgmstream/vgmstream/src/meta/meta.h index 4acb54e4c..19541b025 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/meta.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/meta.h @@ -64,6 +64,7 @@ VGMSTREAM* init_vgmstream_dsp_wiiadpcm(STREAMFILE* sf); VGMSTREAM* init_vgmstream_dsp_cwac(STREAMFILE* sf); VGMSTREAM* init_vgmstream_idsp_tose(STREAMFILE* sf); VGMSTREAM* init_vgmstream_dsp_kwa(STREAMFILE* sf); +VGMSTREAM* init_vgmstream_dsp_apex(STREAMFILE* sf); VGMSTREAM * init_vgmstream_csmp(STREAMFILE *streamFile); @@ -969,4 +970,6 @@ VGMSTREAM* init_vgmstream_lopu_fb(STREAMFILE* sf); VGMSTREAM* init_vgmstream_lpcm_fb(STREAMFILE* sf); +VGMSTREAM* init_vgmstream_wbk(STREAMFILE* sf); + #endif /*_META_H*/ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c b/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c index 7807de138..ddd50a397 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c @@ -1461,7 +1461,6 @@ VGMSTREAM* init_vgmstream_dsp_kwa(STREAMFILE* sf) { if (read_u32be(0x00,sf) != 3) goto fail; - /* .dsp: assumed */ if (!check_extensions(sf, "kwa")) goto fail; @@ -1484,3 +1483,37 @@ VGMSTREAM* init_vgmstream_dsp_kwa(STREAMFILE* sf) { fail: return NULL; } + + +/* APEX - interleaved dsp [Ninja Gaiden 3 Razor's Edge (WiiU)] */ +VGMSTREAM* init_vgmstream_dsp_apex(STREAMFILE* sf) { + dsp_meta dspm = {0}; + uint32_t stream_size; + + /* checks */ + if (!is_id32be(0x00,sf, "APEX")) + goto fail; + + /* .dsp: assumed */ + if (!check_extensions(sf, "dsp")) + goto fail; + + dspm.max_channels = 2; + stream_size = read_u32be(0x04,sf); + /* 0x08: 1? */ + dspm.channels = read_u16be(0x0a,sf); + /* 0x0c: channel size? */ + + dspm.interleave = 0x08; + dspm.header_offset = 0x20; + dspm.header_spacing = 0x60; + dspm.start_offset = dspm.header_offset + dspm.header_spacing * 2; + /* second DSP header exists even for mono files, but has no coefs */ + + dspm.interleave_last = (stream_size / dspm.channels) % dspm.interleave; + + dspm.meta_type = meta_DSP_APEX; + return init_vgmstream_dsp_common(sf, &dspm); +fail: + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c index 401a53ef0..46a21ccb5 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c @@ -1742,7 +1742,7 @@ static int config_bao_version(ubi_bao_header* bao, STREAMFILE* sf) { 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 0x0022000D: /* Just Dance (Wii)-package, We Dare (PS3/Wii)-package */ case 0x0022FF15: /* James Cameron's Avatar: The Game (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/wbk.c b/Frameworks/vgmstream/vgmstream/src/meta/wbk.c new file mode 100644 index 000000000..d884dc11b --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/wbk.c @@ -0,0 +1,155 @@ +#include "meta.h" +#include "../coding/coding.h" + +/* .WBK - seen in some Treyarch games [Spider-Man 2, Ultimate Spider-Man, Call of Duty 2: Big Red One] */ +VGMSTREAM* init_vgmstream_wbk(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + uint32_t table_offset, entry_offset, data_offset, streams_offset, strings_offset, strings_size, coefsec_offset, + name_offset, codec, flags, channels, sound_offset, sound_size, num_samples, sample_rate; + int target_subsong = sf->stream_index, total_subsongs, loop_flag, has_names, i; + + /* checks */ + if (!is_id32be(0x00, sf, "WAVE") || + !is_id32be(0x04, sf, "BK11")) + goto fail; + + /* checks */ + if (!check_extensions(sf, "wbk")) + goto fail; + + /* always little endian, even on GC */ + data_offset = read_u32le(0x10, sf); + //data_size = read_u32le(0x14, sf); + streams_offset = read_u32le(0x18, sf); + //streams_size = read_u32le(0x1c, sf); + + total_subsongs = read_u32le(0x40, sf); + table_offset = read_u32le(0x44, sf); + + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) + goto fail; + + //paramsec_size = read_u32le(0x50, sf); + //paramsec_offset = read_u32le(0x54, sf); + //coefsec_size = read_u32le(0x58, sf); + coefsec_offset = read_u32le(0x5c, sf); + strings_size = read_u32le(0x60, sf); + strings_offset = read_u32le(0x64, sf); + + /* Ultimate Spider-Man has no names, only name hashes */ + { + size_t len; + + /* check if the first sound points at the first or the second string */ + len = read_string(NULL, STREAM_NAME_SIZE, strings_offset, sf); + name_offset = read_u32le(table_offset + 0x00, sf); + has_names = (name_offset == 0x00 || name_offset == len + 0x01); + } + + /* 0x00: name offset/name hash + * 0x04: codec + * 0x05: flags + * 0x06: channel mask (can actually only be mono or stereo) + * 0x07: padding + * 0x08: sound size + * 0x0c: number of samples + * 0x10: group name offset + * 0x14: parameters offset + * 0x18: DSP coefs offset, usually not set (-1) + * 0x1c: sound offset + * 0x20: sample rate + * 0x24: always 0? + * + * struct slightly changed in Call of Duty 2 but still compatible + */ + entry_offset = table_offset + (target_subsong - 1) * 0x28; + name_offset = read_u32le(entry_offset + 0x00, sf); + codec = read_u8(entry_offset + 0x04, sf); + flags = read_u8(entry_offset + 0x05, sf); + channels = read_u8(entry_offset + 0x06, sf) == 0x03 ? 2 : 1; + sound_size = read_u32le(entry_offset + 0x08, sf); + num_samples = read_u32le(entry_offset + 0x0c, sf); + sound_offset = read_u32le(entry_offset + 0x1c, sf); + sample_rate = read_u16le(entry_offset + 0x20, sf); + + if (!(flags & 0x02)) /* streamed sounds have absolute offset */ + sound_offset += data_offset; + + loop_flag = (flags & 0x08); + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + /* fill in the vital statistics */ + vgmstream->meta_type = meta_WBK; + vgmstream->sample_rate = sample_rate; + vgmstream->stream_size = sound_size; + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = 0; + vgmstream->loop_end_sample = num_samples; /* full loops only */ + vgmstream->num_streams = total_subsongs; + + switch (codec) { + case 0x03: { /* DSP */ + uint32_t coef_offset; + uint16_t coef_table[16] = { + 0x0216,0xfc9f,0x026c,0x04b4,0x065e,0xfdec,0x0a11,0xfd1e, + 0x0588,0xfc38,0x05ad,0x01da,0x083b,0xfdbc,0x08c3,0xff18 + }; + + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x8000; + + coef_offset = read_u32le(entry_offset + 0x18, sf); + if (coef_offset == UINT32_MAX || coefsec_offset == 0x00) { + /* hardcoded coef table */ + for (i = 0; i < vgmstream->channels; i++) + memcpy(vgmstream->ch[i].adpcm_coef, coef_table, sizeof(coef_table)); + } else { + if (coefsec_offset == UINT32_MAX) + goto fail; + + dsp_read_coefs_be(vgmstream, sf, coefsec_offset + coef_offset, 0x28); + } + break; + } + case 0x04: /* PSX */ + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x800; + break; + + case 0x05: /* XBOX */ + vgmstream->coding_type = coding_XBOX_IMA; + vgmstream->layout_type = layout_none; + break; + + case 0x07: /* IMA */ + vgmstream->coding_type = coding_IMA; + vgmstream->layout_type = layout_none; + + /* for some reason, number of samples is off for IMA */ + vgmstream->num_samples = ima_bytes_to_samples(sound_size, channels); + vgmstream->loop_end_sample = vgmstream->num_samples; + break; + + default: + goto fail; + } + + if (has_names) + read_string(vgmstream->stream_name, STREAM_NAME_SIZE, strings_offset + name_offset, sf); + else + snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%08x", name_offset); + + if (!vgmstream_open_stream(vgmstream, sf, sound_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} \ No newline at end of file diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index 8e14055ed..25a5dd1e7 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -525,6 +525,8 @@ VGMSTREAM* (*init_vgmstream_functions[])(STREAMFILE* sf) = { init_vgmstream_psb, init_vgmstream_lopu_fb, init_vgmstream_lpcm_fb, + init_vgmstream_wbk, + init_vgmstream_dsp_apex, /* 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 e787bbd55..1be7422de 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h @@ -761,6 +761,8 @@ typedef enum { meta_PSB, meta_LOPU_FB, meta_LPCM_FB, + meta_WBK, + meta_DSP_APEX, } meta_t;