From 651a66624e835d70335831411b92c861b8976b22 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 19 Aug 2023 23:28:05 +0200 Subject: [PATCH 1/5] Add ADM2 .wem [The Grand Tour Game (PC)] --- src/formats.c | 2 +- src/meta/adm3.c | 163 +++++++++++++++++++++++++++++------------- src/meta/meta.h | 1 + src/vgmstream.c | 1 + src/vgmstream_types.h | 2 +- 5 files changed, 119 insertions(+), 50 deletions(-) diff --git a/src/formats.c b/src/formats.c index 0a397498..49719912 100644 --- a/src/formats.c +++ b/src/formats.c @@ -1402,7 +1402,7 @@ static const meta_info meta_info_list[] = { {meta_SSPF, "Konami SSPF header"}, {meta_S3V, "Konami S3V header"}, {meta_ESF, "Eurocom ESF header"}, - {meta_ADM3, "Crankcase ADM3 header"}, + {meta_ADM, "Crankcase ADMx header"}, {meta_TT_AD, "Traveller's Tales AUDIO_DATA header"}, {meta_SNDZ, "Sony SNDZ header"}, {meta_VAB, "Sony VAB header"}, diff --git a/src/meta/adm3.c b/src/meta/adm3.c index f38ed06b..d38fd598 100644 --- a/src/meta/adm3.c +++ b/src/meta/adm3.c @@ -5,6 +5,7 @@ typedef struct { int total_subsongs; int target_subsong; + int version; uint32_t stream_offset; uint32_t stream_size; @@ -13,51 +14,70 @@ typedef struct { int sample_rate; int channels; int32_t num_samples; -} adm3_header_t; +} adm_header_t; -static int parse_adm3(adm3_header_t* adm3, STREAMFILE* sf); +static int parse_adm(adm_header_t* adm, STREAMFILE* sf); +static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int version); + +/* ADM2 - Crankcase Audio REV plugin file [The Grand Tour Game (PC)] */ +VGMSTREAM* init_vgmstream_adm2(STREAMFILE* sf) { + + /* checks */ + if (!is_id32be(0x00,sf, "ADM2")) + return NULL; + if (!check_extensions(sf, "wem")) + return NULL; + + return init_vgmstream_adm(sf, 2); +} /* ADM3 - Crankcase Audio REV plugin file [Cyberpunk 2077 (PC), MotoGP 21 (PC)] */ VGMSTREAM* init_vgmstream_adm3(STREAMFILE* sf) { - VGMSTREAM* vgmstream = NULL; - adm3_header_t adm3 = {0}; - /* checks */ if (!is_id32be(0x00,sf, "ADM3")) - goto fail; + return NULL; if (!check_extensions(sf, "wem")) - goto fail; + return NULL; - adm3.target_subsong = sf->stream_index; - if (adm3.target_subsong == 0) adm3.target_subsong = 1; + return init_vgmstream_adm(sf, 3); +} - /* ADM3 are files used with the Wwise Crankaudio plugin, that simulate engine noises with +static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int version) { + VGMSTREAM* vgmstream = NULL; + adm_header_t adm = {0}; + + /* ADMx are files used with the Wwise Crankaudio plugin, that simulate engine noises with * base internal samples and some internal RPM config (probably). Actual file seems to * define some combo of samples, this only plays those separate samples. * Decoder is basically Apple's IMA (internally just "ADPCMDecoder") but transforms to float * each sample during decode by multiplying by 0.000030518509 */ - if (!parse_adm3(&adm3, sf)) + adm.target_subsong = sf->stream_index; + if (adm.target_subsong == 0) adm.target_subsong = 1; + + adm.version = version; + + if (!parse_adm(&adm, sf)) goto fail; /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(adm3.channels, adm3.loop_flag); + vgmstream = allocate_vgmstream(adm.channels, adm.loop_flag); if (!vgmstream) goto fail; - vgmstream->meta_type = meta_ADM3; - vgmstream->sample_rate = adm3.sample_rate; - vgmstream->num_samples = adm3.num_samples; /* slightly lower than bytes-to-samples */ - vgmstream->num_streams = adm3.total_subsongs; - vgmstream->stream_size = adm3.stream_size; + vgmstream->meta_type = meta_ADM; + vgmstream->sample_rate = adm.sample_rate; + vgmstream->num_samples = adm.num_samples; /* slightly lower than bytes-to-samples */ + vgmstream->num_streams = adm.total_subsongs; + vgmstream->stream_size = adm.stream_size; vgmstream->coding_type = coding_APPLE_IMA4; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x22; - if (!vgmstream_open_stream(vgmstream, sf, adm3.stream_offset)) + if (!vgmstream_open_stream(vgmstream, sf, adm.stream_offset)) goto fail; return vgmstream; @@ -67,11 +87,37 @@ fail: } -static int parse_type(adm3_header_t* adm3, STREAMFILE* sf, uint32_t offset) { +static int parse_type(adm_header_t* adm, STREAMFILE* sf, uint32_t offset) { - if (is_id32be(offset, sf, "RMP1")) { + /* ADM2 chunks */ + if (is_id32be(offset, sf, "GRN1")) { + /* 0x74: offset to floats? */ + offset = read_u32le(offset + 0x78, sf); /* to SMP1 */ + if (!parse_type(adm, sf, offset)) + goto fail; + } + else if (is_id32be(offset, sf, "SMP1")) { + adm->total_subsongs++; + + if (adm->target_subsong == adm->total_subsongs) { + /* 0x04 always 0 */ + /* 0x08 version? (0x00030000) */ + adm->channels = read_u16le(offset + 0x0c, sf); + /* 0x0e 0x0001? */ + /* 0x10 header size (0x2c) */ + adm->sample_rate = read_s32le(offset + 0x14, sf); + adm->num_samples = read_s32le(offset + 0x18, sf); + adm->stream_size = read_u32le(offset + 0x1c, sf); + adm->stream_offset = read_u32le(offset + 0x20, sf); + /* rest: null */ + VGM_LOG("so=%x %x\n", adm->stream_size, adm->stream_offset); + } + } + + /* ADM3 chunks */ + else if (is_id32be(offset, sf, "RMP1")) { offset = read_u32le(offset + 0x1c, sf); - if (!parse_type(adm3, sf, offset)) + if (!parse_type(adm, sf, offset)) goto fail; /* 0x24: offset to GRN1 */ } @@ -87,30 +133,30 @@ static int parse_type(adm3_header_t* adm3, STREAMFILE* sf, uint32_t offset) { if (smp2_unk != 1) goto fail; - if (!parse_type(adm3, sf, smp2_offset)) /* SMP2 */ + if (!parse_type(adm, sf, smp2_offset)) /* SMP2 */ goto fail; } } else if (is_id32be(offset, sf, "SMP2")) { - adm3->total_subsongs++; + adm->total_subsongs++; - if (adm3->target_subsong == adm3->total_subsongs) { + if (adm->target_subsong == adm->total_subsongs) { /* 0x04 always 0 */ - /* 0x08 always 0x00040000 */ - adm3->channels = read_u32le(offset + 0x0c, sf); + /* 0x08 version? (0x00040000) */ + adm->channels = read_u32le(offset + 0x0c, sf); /* usually 4, with different sounds*/ /* 0x10 float pitch? */ /* 0x14 int pitch? */ /* 0x18 0x0001? */ - /* 0x1a 0x0030? (header size?) */ - adm3->sample_rate = read_s32le(offset + 0x1c, sf); - adm3->num_samples = read_s32le(offset + 0x20, sf); - adm3->stream_size = read_u32le(offset + 0x24, sf); + /* 0x1a header size (0x30) */ + adm->sample_rate = read_s32le(offset + 0x1c, sf); + adm->num_samples = read_s32le(offset + 0x20, sf); + adm->stream_size = read_u32le(offset + 0x24, sf); /* 0x28 1? */ - adm3->stream_offset = read_u32le(offset + 0x2c, sf); + adm->stream_offset = read_u32le(offset + 0x2c, sf); } } else { - VGM_LOG("ADM3: unknown at %x\n", offset); + VGM_LOG("ADM: unknown at %x\n", offset); goto fail; } @@ -119,31 +165,52 @@ fail: return 0; } -static int parse_adm3(adm3_header_t* adm3, STREAMFILE* sf) { +static int parse_adm(adm_header_t* adm, STREAMFILE* sf) { uint32_t offset; /* 0x04: null */ - /* 0x08: version? */ + /* 0x08: version? (ADM2: 0x00050000, ADM3: 0x00060000) */ /* 0x0c: header size */ /* 0x10: data start */ - /* rest unknown, looks mostly the same between files */ + /* rest unknown, looks mostly the same between files (some floats and stuff) */ - /* higher ramp, N samples from low to high */ - offset = read_u32le(0x0FC, sf); - if (!parse_type(adm3, sf, offset)) goto fail; /* RMP1 */ - if (read_u32le(0x100, sf) != 1) goto fail; + switch(adm->version) { + case 2: + /* low to high */ + offset = read_u32le(0x104, sf); + if (!parse_type(adm, sf, offset)) goto fail; /* GRN1 */ - /* lower ramp, also N samples */ - offset = read_u32le(0x104, sf); - if (!parse_type(adm3, sf, offset)) goto fail; /* RMP1 */ - if (read_u32le(0x108, sf) != 1) goto fail; + /* high to low */ + offset = read_u32le(0x108, sf); + if (!parse_type(adm, sf, offset)) goto fail; /* GRN1 */ - /* idle engine */ - offset = read_u32le(0x10c, sf); - if (!parse_type(adm3, sf, offset)) goto fail; /* SMP2 */ - if (read_u32le(0x110, sf) != 1) goto fail; + /* idle engine */ + offset = read_u32le(0x10c, sf); + if (!parse_type(adm, sf, offset)) goto fail; /* SMP1 */ + break; - if (adm3->target_subsong < 0 || adm3->target_subsong > adm3->total_subsongs || adm3->total_subsongs < 1) + case 3: + /* higher ramp, N samples from low to high */ + offset = read_u32le(0x0FC, sf); + if (!parse_type(adm, sf, offset)) goto fail; /* RMP1 */ + if (read_u32le(0x100, sf) != 1) goto fail; + + /* lower ramp, also N samples */ + offset = read_u32le(0x104, sf); + if (!parse_type(adm, sf, offset)) goto fail; /* RMP1 */ + if (read_u32le(0x108, sf) != 1) goto fail; + + /* idle engine */ + offset = read_u32le(0x10c, sf); + if (!parse_type(adm, sf, offset)) goto fail; /* SMP2 */ + if (read_u32le(0x110, sf) != 1) goto fail; + break; + + default: + goto fail; + } + + if (adm->target_subsong < 0 || adm->target_subsong > adm->total_subsongs || adm->total_subsongs < 1) goto fail; return 1; diff --git a/src/meta/meta.h b/src/meta/meta.h index 56ca00f7..61ace4d5 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -953,6 +953,7 @@ VGMSTREAM* init_vgmstream_s3v(STREAMFILE* sf); VGMSTREAM* init_vgmstream_esf(STREAMFILE* sf); +VGMSTREAM* init_vgmstream_adm2(STREAMFILE* sf); VGMSTREAM* init_vgmstream_adm3(STREAMFILE* sf); VGMSTREAM* init_vgmstream_tt_ad(STREAMFILE* sf); diff --git a/src/vgmstream.c b/src/vgmstream.c index 9cc129f0..208b8222 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -522,6 +522,7 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_squeakstream, init_vgmstream_squeaksample, init_vgmstream_snds, + init_vgmstream_adm2, /* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */ init_vgmstream_scd_pcm, diff --git a/src/vgmstream_types.h b/src/vgmstream_types.h index 9e5c053a..73c333bd 100644 --- a/src/vgmstream_types.h +++ b/src/vgmstream_types.h @@ -691,7 +691,7 @@ typedef enum { meta_SSPF, meta_S3V, meta_ESF, - meta_ADM3, + meta_ADM, meta_TT_AD, meta_SNDZ, meta_VAB, From fbcd503bd7212244cf0223e4ba8bdd6a8048bcdd Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 19 Aug 2023 23:30:25 +0200 Subject: [PATCH 2/5] cleanup: adm3.c to adm.c --- src/libvgmstream.vcxproj | 2 +- src/libvgmstream.vcxproj.filters | 2 +- src/meta/{adm3.c => adm.c} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/meta/{adm3.c => adm.c} (100%) diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index 9e8b13da..dfef25a8 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -337,7 +337,7 @@ - + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 726b131d..bd93d3ea 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -832,7 +832,7 @@ meta\Source Files - + meta\Source Files diff --git a/src/meta/adm3.c b/src/meta/adm.c similarity index 100% rename from src/meta/adm3.c rename to src/meta/adm.c From 64bc7ac18c3e7f0a9559d2f8aaa0090e39224148 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 19 Aug 2023 23:30:35 +0200 Subject: [PATCH 3/5] doc --- doc/FORMATS.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/FORMATS.md b/doc/FORMATS.md index c583a181..ab20740b 100644 --- a/doc/FORMATS.md +++ b/doc/FORMATS.md @@ -273,7 +273,7 @@ different internally (encrypted, different versions, etc) and not always can be - RIFX WAVE header (smpl looping) [*RIFX_WAVE_smpl*] - *riff*: `.wav .lwav .xwav .mwv .da .dax .cd .med .snd .adx .adp .xss .xsew .adpcm .adw .wd .(extensionless) .sbv .wvx .str .at3 .rws .aud .at9 .ckd .saf .ima .nsa .pcm .xvag .ogg .logg .p1d .xms .mus .dat .ldat` - *rifx*: `.wav .lwav` - - Codecs: AICA_int PCM24LE PCM16BE PCM16LE PCM8_U MSADPCM IMA MS_IMA AICA MPEG_custom XBOX_IMA MS_IMA_3BIT DVI_IMA L5_555 OGG_VORBIS ATRAC9 ATRAC3 MPEG MSADPCM_int + - Codecs: AICA_int PCM32LE PCM24LE PCM16BE PCM16LE PCM8_U MSADPCM IMA PCMFLOAT MS_IMA AICA MPEG_custom XBOX_IMA MS_IMA_3BIT DVI_IMA L5_555 OGG_VORBIS ATRAC9 ATRAC3 MPEG MSADPCM_int - **nwa.c** - VisualArt's NWA header (NWAINFO.INI looping) [*NWA_NWAINFOINI*] - VisualArt's NWA header (Gameexe.ini looping) [*NWA_GAMEEXEINI*] @@ -1735,8 +1735,9 @@ different internally (encrypted, different versions, etc) and not always can be - Eurocom ESF header [*ESF*] - *esf*: `.esf` - Codecs: DVI_IMA PCM8_U PCM16LE -- **adm3.c** - - Crankcase ADM3 header [*ADM3*] +- **adm.c** + - Crankcase ADMx header [*ADM*] + - *adm2*: `.wem` - *adm3*: `.wem` - Codecs: APPLE_IMA4 - **tt_ad.c** From 046918589a55f62e251fc91c05e088fdd9503f59 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 19 Aug 2023 23:31:40 +0200 Subject: [PATCH 4/5] Add .ogg loops [Tsuki ni Yorisou Otome no Sahou (PC)] --- src/meta/ogg_vorbis.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/meta/ogg_vorbis.c b/src/meta/ogg_vorbis.c index d9e24289..e1b84c02 100644 --- a/src/meta/ogg_vorbis.c +++ b/src/meta/ogg_vorbis.c @@ -614,6 +614,7 @@ static VGMSTREAM* _init_vgmstream_ogg_vorbis_config(STREAMFILE* sf, off_t start, const char* comment = NULL; while (ogg_vorbis_get_comment(data, &comment)) { + ;VGM_LOG("OGG: user_comment=%s\n", comment); if (strstr(comment,"loop_start=") == comment || /* Phantasy Star Online: Blue Burst (PC) (no loop_end pair) */ strstr(comment,"LOOP_START=") == comment || /* Phantasy Star Online: Blue Burst (PC), common */ @@ -702,18 +703,26 @@ static VGMSTREAM* _init_vgmstream_ogg_vorbis_config(STREAMFILE* sf, off_t start, force_seek = 1; } + else if (strstr(comment,"COMMENT=*loopsample,") == comment) { /* Tsuki ni Yorisou Otome no Sahou (PC) */ + int unk0; // always 0 (delay?) + int unk1; // always -1 (loop flag? but non-looped files have no comment) + int m = sscanf(comment,"COMMENT=*loopsample,%d,%d,%d,%d", &unk0, &loop_start, &loop_end, &unk1); + if (m == 4) { + loop_flag = 1; + loop_end_found = 1; + } + } + /* Hatsune Miku Project DIVA games, though only 'Arcade Future Tone' has >4ch files * ENCODER tag is common but ogg_vorbis_encode looks unique enough * (arcade ends with "2010-11-26" while consoles have "2011-02-07" */ - if (strstr(comment, "ENCODER=ogg_vorbis_encode/") == comment) { + else if (strstr(comment, "ENCODER=ogg_vorbis_encode/") == comment) { disable_reordering = 1; } - if (strstr(comment, "TITLE=") == comment) { + else if (strstr(comment, "TITLE=") == comment) { strncpy(name, comment + 6, sizeof(name) - 1); } - - ;VGM_LOG("OGG: user_comment=%s\n", comment); } } From a7dab77b7703c6a78175b3b6d933e7d8bef66a65 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 19 Aug 2023 23:35:28 +0200 Subject: [PATCH 5/5] Fix foobar .txtp to .ogg issues in rare cases --- fb2k/foo_vgmstream.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fb2k/foo_vgmstream.cpp b/fb2k/foo_vgmstream.cpp index 928e483f..2f19c340 100644 --- a/fb2k/foo_vgmstream.cpp +++ b/fb2k/foo_vgmstream.cpp @@ -7,6 +7,7 @@ #endif #include #include +#include #include @@ -351,12 +352,22 @@ bool input_vgmstream::g_is_our_path(const char * p_path, const char * p_extensio VGMSTREAM* input_vgmstream::init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort) { VGMSTREAM* vgmstream = NULL; + /* Workaround for a foobar bug (mainly for complex TXTP): + * When converting to .ogg foobar calls oggenc, that calls setlocale(LC_ALL, "") to use system's locale. + * After that, text parsing using sscanf that expects US locale for "N.N" decimals fails in some locales, + * so reset it here just in case + * (maybe should be done on lib and/or restore original locale but it's not common to change it in C) */ + //const char* original_locale = setlocale(LC_ALL, NULL); + setlocale(LC_ALL, "C"); + STREAMFILE* sf = open_foo_streamfile(filename, &p_abort, &stats); if (sf) { sf->stream_index = p_subsong; vgmstream = init_vgmstream_from_STREAMFILE(sf); close_streamfile(sf); } + + //setlocale(LC_ALL, original_locale); return vgmstream; }