diff --git a/src/formats.c b/src/formats.c index 84bb04cb..34b004df 100644 --- a/src/formats.c +++ b/src/formats.c @@ -523,6 +523,7 @@ static const char* extension_list[] = { "sps", "spsd", "spw", + "srsa", "ss2", "ssd", //txth/reserved [Zack & Wiki (Wii)] "ssm", diff --git a/src/meta/ktsr.c b/src/meta/ktsr.c index 0ef57293..bfed0804 100644 --- a/src/meta/ktsr.c +++ b/src/meta/ktsr.c @@ -7,6 +7,7 @@ typedef enum { NONE, MSADPCM, DSP, GCADPCM, ATRAC9, RIFF_ATRAC9, KOVS, KTSS, } k #define MAX_CHANNELS 8 typedef struct { + bool is_srsa; int total_subsongs; int target_subsong; ktsr_codec codec; @@ -31,12 +32,53 @@ typedef struct { char name[255+1]; } ktsr_header; +static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa); 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); static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, ktsr_header* ktsr, VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf), const char* ext); /* KTSR - Koei Tecmo sound resource container */ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) { + return init_vgmstream_ktsr_internal(sf, false) ; +} + +/* ASRS - container of KTSR found in newer games */ +VGMSTREAM* init_vgmstream_asrs(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE* temp_sf = NULL; + + /* checks */ + if (!is_id32be(0x00, sf, "ASRS")) + return NULL; + /* 0x04: null */ + /* 0x08: file size */ + /* 0x0c: null */ + + /* .srsa: header id (as generated by common tools) */ + if (!check_extensions(sf, "srsa")) + return NULL; + + + /* mini-container of memory-KTSR (streams also have a "TSRS" for stream-KTSR). + * .srsa/srst usually have hashed filenames, so it isn't easy to match them, so this is mainly useful + * for .srsa with internal streams. */ + uint32_t subfile_offset = 0x10; + uint32_t subfile_size = get_streamfile_size(sf) - subfile_offset; + + /* use a subfile b/c it's a pain to change all the offsets below and it's also how engine would do it */ + temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, "ktsl2asbin"); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_ktsr_internal(temp_sf, true); + close_streamfile(temp_sf); + return vgmstream; +fail: + close_streamfile(temp_sf); + return NULL; +} + + +static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa) { VGMSTREAM* vgmstream = NULL; STREAMFILE* sf_b = NULL; ktsr_header ktsr = {0}; @@ -46,20 +88,22 @@ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) { /* checks */ if (!is_id32be(0x00, sf, "KTSR")) - goto fail; - if (read_u32be(0x04, sf) != 0x777B481A) /* hash(?) id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */ - goto fail; + return NULL; + if (read_u32be(0x04, sf) != 0x777B481A) /* hash id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */ + return NULL; + /* .ktsl2asbin: common [Atelier Ryza (PC/Switch), Nioh (PC)] * .asbin: Warriors Orochi 4 (PC) */ if (!check_extensions(sf, "ktsl2asbin,asbin")) - goto fail; + return NULL; /* KTSR can be a memory file (ktsl2asbin), streams (ktsl2stbin) and global config (ktsl2gcbin) * This accepts .ktsl2asbin with internal data or external streams as subsongs. - * Some info from KTSR.bt */ + * Hashes are meant to be read LE, but here are BE for easier comparison. Some info from KTSR.bt */ if (target_subsong == 0) target_subsong = 1; ktsr.target_subsong = target_subsong; + ktsr.is_srsa = is_srsa; if (!parse_ktsr(&ktsr, sf)) goto fail; @@ -67,6 +111,9 @@ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) { /* open companion body */ if (ktsr.is_external) { const char* companion_ext = check_extensions(sf, "asbin") ? "stbin" : "ktsl2stbin"; + if (ktsr.is_srsa) + companion_ext = "srst"; + sf_b = open_streamfile_by_ext(sf, companion_ext); if (!sf_b) { vgm_logi("KTSR: companion file '*.%s' not found\n", companion_ext); @@ -253,6 +300,7 @@ static int parse_codec(ktsr_header* ktsr) { /* platform + format to codec, simplified until more codec combos are found */ switch(ktsr->platform) { case 0x01: /* PC */ + case 0x05: /* PC/Steam [Fate/Samurai Remnant (PC)] */ if (ktsr->is_external) { if (ktsr->format == 0x0005) ktsr->codec = KOVS; // Atelier Ryza (PC) @@ -483,7 +531,7 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { * 08: version? * 0a: unknown (usually 00, 02/03 seen in Vita) * 0b: platform (01=PC, 03=Vita, 04=Switch) - * 0c: game id? + * 0c: audio id? (seen in multiple files/games and used as Ogg stream IDs) * 10: null * 14: null * 18: file size @@ -506,8 +554,8 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* 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 0x6172DBA8: /* ? (mostly empty) */ + case 0xBD888C36: /* cue? (floats, stream id, etc, may have extended name; can have sub-chunks)-appears N times */ case 0xC9C48EC1: /* unknown (has some string inside like "boss") */ case 0xA9D23BF1: /* "state container", some kind of config/floats, witgh names like 'State_bgm01'..N */ case 0x836FBECA: /* unknown (~0x300, encrypted? table + data) */ @@ -566,6 +614,13 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { parse_longname(ktsr, sf, stream_id); build_name(ktsr, sf); + /* skip TSRS header */ + if (ktsr->is_external && ktsr->is_srsa) { + for (int i = 0; i < ktsr->channels; i++) { + ktsr->stream_offsets[i] += 0x10; + } + } + return 1; fail: vgm_logi("KTSR: unknown variation (report)\n"); diff --git a/src/meta/meta.h b/src/meta/meta.h index 00cac342..8b97a6af 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -884,6 +884,7 @@ VGMSTREAM* init_vgmstream_diva(STREAMFILE* sf); VGMSTREAM* init_vgmstream_imuse(STREAMFILE* sf); VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf); +VGMSTREAM* init_vgmstream_asrs(STREAMFILE* sf); VGMSTREAM* init_vgmstream_mups(STREAMFILE* sf); diff --git a/src/vgmstream.c b/src/vgmstream.c index 7f65bd76..ee9c1580 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -458,6 +458,7 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_diva, init_vgmstream_imuse, init_vgmstream_ktsr, + init_vgmstream_asrs, init_vgmstream_mups, init_vgmstream_kat, init_vgmstream_pcm_success,