From f6b2c6fab39cbd1beaae4a4dbcb0e32327b7d9f1 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sun, 21 Jul 2024 09:50:28 +0200 Subject: [PATCH] Fix some .agsc [Metroid Prime (GC)] --- src/meta/agsc.c | 195 ++++++++++++++++++++++++++++-------------- src/util/meta_utils.c | 6 +- src/util/meta_utils.h | 6 +- 3 files changed, 143 insertions(+), 64 deletions(-) diff --git a/src/meta/agsc.c b/src/meta/agsc.c index e19922bc..63170a76 100644 --- a/src/meta/agsc.c +++ b/src/meta/agsc.c @@ -1,74 +1,145 @@ #include "meta.h" -#include "../util.h" +#include "../coding/coding.h" +#include "../util/reader_text.h" +#include "../util/meta_utils.h" -/* .agsc - from Metroid Prime 2 */ +static bool parse_agsc(meta_header_t* hdr, STREAMFILE* sf, int version); -VGMSTREAM * init_vgmstream_agsc(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - char filename[PATH_LIMIT]; - off_t header_offset; - off_t start_offset; - int channel_count; - int i; +/* .agsc - from Retro Studios games [Metroid Prime (GC), Metroid Prime 2 (GC)] */ +VGMSTREAM* init_vgmstream_agsc(STREAMFILE* sf) { - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("agsc",filename_extension(filename))) goto fail; + /* checks */ + int version; + if (is_id32be(0x00, sf, "Audi")) + version = 1; + else if (read_u32be(0x00, sf) == 0x00000001) + version = 2; + else + return NULL; - /* check header */ - if ((uint32_t)read_32bitBE(0,streamFile)!=0x00000001) - goto fail; + /* .agsc: 'class' type in .pak */ + if (!check_extensions(sf, "agsc")) + return NULL; - /* count length of name, including terminating 0 */ - for (header_offset=4;header_offset < get_streamfile_size(streamFile) && read_8bit(header_offset,streamFile)!='\0';header_offset++); - header_offset ++; + meta_header_t hdr = {0}; + if (!parse_agsc(&hdr, sf, version)) + return NULL; - channel_count = 1; + hdr.meta = meta_AGSC; + hdr.coding = coding_NGC_DSP; + hdr.layout = layout_none; + hdr.big_endian = true; + hdr.allow_dual_stereo = true; - /* build the VGMSTREAM */ + hdr.sf = sf; + hdr.open_stream = true; - vgmstream = allocate_vgmstream(1,1); - if (!vgmstream) goto fail; - - /* fill in the vital statistics */ - vgmstream->num_samples = read_32bitBE(header_offset+0xda,streamFile); - vgmstream->sample_rate = (uint16_t)read_16bitBE(header_offset+0xd8,streamFile); - - vgmstream->loop_start_sample = read_32bitBE(header_offset+0xde,streamFile); - /* this is cute, we actually have a "loop length" */ - vgmstream->loop_end_sample = (vgmstream->loop_start_sample + read_32bitBE(header_offset+0xe2,streamFile))-1; - - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_none; - vgmstream->meta_type = meta_AGSC; - vgmstream->allow_dual_stereo = 1; - - for (i=0;i<16;i++) { - vgmstream->ch[0].adpcm_coef[i]=read_16bitBE(header_offset+0xf6+i*2,streamFile); - } - - start_offset = header_offset+0x116; - - /* open the file for reading by each channel */ - { - int i; - for (i=0;ich[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - - if (!vgmstream->ch[i].streamfile) goto fail; - - vgmstream->ch[i].channel_start_offset= - vgmstream->ch[i].offset= - start_offset; - } - } - - return vgmstream; - - /* clean up anything we may have opened */ -fail: - if (vgmstream) close_vgmstream(vgmstream); - return NULL; + return alloc_metastream(&hdr); +} + + +static bool parse_agsc(meta_header_t* hdr, STREAMFILE* sf, int version) { + uint32_t offset; + int name_size; + + switch(version) { + case 1: + // usually "Audio/" but rarely "Audio//" + name_size = read_string(NULL, 0x20, 0x00, sf); + if (name_size == 0) // not a string + return false; + offset = name_size + 1; + break; + + case 2: + /* after fixed ID */ + offset = 0x04; + break; + + default: + return false; + } + + /* after id starts with name + null */ + hdr->name_offset = offset; + name_size = read_string(NULL, 0x20, offset, sf); + if (name_size == 0) // not a string + return false; + offset += name_size + 1; + + uint32_t head_offset, data_offset; + uint32_t unk1_size, unk2_size, head_size, data_size; + switch(version) { + case 1: + /* per chunk: chunk size + chunk data */ + unk1_size = read_u32be(offset, sf); + offset += 0x04 + unk1_size; + + unk2_size = read_u32be(offset, sf); + offset += 0x04 + unk2_size; + + data_offset = offset + 0x04; // data chunk goes before headers... + data_size = read_u32be(offset, sf); + offset += 0x04 + data_size; + + head_offset = offset + 0x04; + head_size = read_u32be(offset, sf); + offset += 0x04 + head_size; + + break; + + case 2: + /* chunk sizes per chunk + chunk data per chunk */ + offset += 0x02; // song id? + unk1_size = read_u32be(offset + 0x00, sf); + unk2_size = read_u32be(offset + 0x04, sf); + head_size = read_u32be(offset + 0x08, sf); + data_size = read_u32be(offset + 0x0c, sf); + + head_offset = offset + 0x10 + unk1_size + unk2_size; + data_offset = head_offset + head_size; + + break; + default: + return false; + } + // offsets/values/data aren't 32b aligned but file is (0xFF padding) + + // possible in some test banks + if (data_size == 0 || head_size < 0x20 + 0x28) { + vgm_logi("AGSC: bank has no subsongs (ignore)\n"); + return false; + } + + /* header chunk has 0x20 headers per subsong + 0xFFFFFFFF (end marker) + 0x28 coefs per subsongs, + * no apparent count even in other chunks */ + hdr->total_subsongs = (head_size - 0x04) / (0x20 + 0x28); + hdr->target_subsong = sf->stream_index; + if (!check_subsongs(&hdr->target_subsong, hdr->total_subsongs)) + return false; + + uint32_t entry_offset = head_offset + 0x20 * (hdr->target_subsong - 1); + + // 00: id? + hdr->stream_offset = read_u32be(entry_offset + 0x04,sf) + data_offset; + // 08: null? + // 0c: always 0x3c00? + hdr->sample_rate = read_u16be(entry_offset + 0x0e,sf); + hdr->num_samples = read_s32be(entry_offset + 0x10,sf); + hdr->loop_start = read_s32be(entry_offset + 0x14,sf); + hdr->loop_end = read_s32be(entry_offset + 0x18,sf); // loop length + hdr->coefs_offset = read_s32be(entry_offset + 0x1c,sf); + + if (hdr->loop_end) + hdr->loop_end = hdr->loop_end + hdr->loop_start - 1; + hdr->loop_flag = hdr->loop_end != 0; + + hdr->coefs_offset += head_offset + 0x08; // skip unknown hist/loop ps-like values + + hdr->channels = 1; // MP2 uses dual stereo for title track + hdr->stream_size = hdr->num_samples / 14 * 8 * hdr->channels; // meh + + return true; } diff --git a/src/util/meta_utils.c b/src/util/meta_utils.c index 7d611dec..fdf5235a 100644 --- a/src/util/meta_utils.c +++ b/src/util/meta_utils.c @@ -1,5 +1,6 @@ #include "../vgmstream.h" #include "meta_utils.h" +#include "reader_text.h" /* Allocate memory and setup a VGMSTREAM */ @@ -10,7 +11,7 @@ VGMSTREAM* alloc_metastream(meta_header_t* h) { return NULL; } if (h->num_samples <= 0 || h->num_samples > VGMSTREAM_MAX_NUM_SAMPLES) { - VGM_LOG("meta: wrong samples %i\n", h->sample_rate); + VGM_LOG("meta: wrong samples %i\n", h->num_samples); return NULL; } @@ -30,7 +31,10 @@ VGMSTREAM* alloc_metastream(meta_header_t* h) { vgmstream->num_streams = h->total_subsongs; vgmstream->stream_size = h->stream_size; vgmstream->interleave_block_size = h->interleave; + vgmstream->allow_dual_stereo = h->allow_dual_stereo; + if (h->name_offset) + read_string(vgmstream->stream_name, sizeof(vgmstream->stream_name), h->name_offset, h->sf ? h->sf : h->sf_head); if (h->coding == coding_NGC_DSP && (h->sf || h->sf_head)) { if (h->coefs_offset || h->coefs_spacing) diff --git a/src/util/meta_utils.h b/src/util/meta_utils.h index a8676f50..4c3b37bc 100644 --- a/src/util/meta_utils.h +++ b/src/util/meta_utils.h @@ -39,7 +39,9 @@ typedef struct { uint32_t hists_offset; uint32_t hists_spacing; - /* optional but can be used for some actions */ + uint32_t name_offset; + + /* optional but can be used for some actions (such as DSP coefs) */ bool big_endian; coding_t coding; layout_t layout; @@ -51,6 +53,8 @@ typedef struct { STREAMFILE* sf_body; bool open_stream; + + bool allow_dual_stereo; } meta_header_t; VGMSTREAM* alloc_metastream(meta_header_t* h);