ktsr: add .txtm support

This commit is contained in:
bnnm 2023-10-14 16:58:55 +02:00
parent cc29ab1058
commit 5928c8df0d
3 changed files with 58 additions and 43 deletions

View File

@ -1600,8 +1600,9 @@ different internally (encrypted, different versions, etc) and not always can be
- Codecs: IMUSE
- **ktsr.c**
- Koei Tecmo KTSR header [*KTSR*]
- *ktsr*: `.ktsl2asbin .asbin`
- *asrs*: `.srsa`
- *ktsr_internal*: `.ktsl2asbin .asbin`
- *ktsr_internal*
- Subfiles: *riff ogg_vorbis ktss*
- Codecs: MSADPCM_int NGC_DSP ATRAC9
- **mups.c**

View File

@ -513,6 +513,10 @@ willow.mpf: willow.mus,willow_o.mus
bgm_2_streamfiles.awb: bgm_2.acb
```
```
# hashes of SE1_Common_BGM + ext [Hyrule Warriors: Age of Calamity (Switch)]
0x3a160928.srsa: 0x272c6efb.srsa
```
```
# Snack World (Switch) names for .awb (single .acb for all .awb, order matters)
bgm.awb: bgm.acb
bgm_DLC1.awb: bgm.acb
@ -639,7 +643,7 @@ called to play one or multiple audio "waves"/"materials" in another section.
Rather than handling cues, vgmstream shows and plays waves, then assigns cue names
that point to the wave if possible, since vgmstream mainly deals with streamed/wave
audio and simulating cues is out of scope. Figuring out a whole cue format can be a
*huge* time investment, so handling waves only is often enough.
*huge* time investment, so handling waves only is good enough.
Cues can be *very* complex, like N cues pointing to 1 wave with varying pitch, or
1 cue playing one random wave out of 3. Sometimes not all waves are referenced by

View File

@ -1,12 +1,14 @@
#include "meta.h"
#include "../coding/coding.h"
#include "../layout/layout.h"
#include "../util/companion_files.h"
typedef enum { NONE, MSADPCM, DSP, GCADPCM, ATRAC9, RIFF_ATRAC9, KOVS, KTSS, } ktsr_codec;
#define MAX_CHANNELS 8
typedef struct {
uint32_t base_offset;
bool is_srsa;
int total_subsongs;
int target_subsong;
@ -43,13 +45,22 @@ static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, ktsr_header* ktsr, V
/* KTSR - Koei Tecmo sound resource container */
VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00, sf, "KTSR"))
return NULL;
/* others: see internal */
/* .ktsl2asbin: common [Atelier Ryza (PC/Switch), Nioh (PC)]
* .asbin: Warriors Orochi 4 (PC) */
if (!check_extensions(sf, "ktsl2asbin,asbin"))
return NULL;
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"))
@ -58,27 +69,15 @@ VGMSTREAM* init_vgmstream_asrs(STREAMFILE* sf) {
/* 0x08: file size */
/* 0x0c: null */
/* .srsa: header id (as generated by common tools) */
/* .srsa: header id (as generated by common tools, probably "(something)asbin") */
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;
* .srsa/srst usually have hashed filenames, so it isn't easy to match them, so this
* is mainly useful for .srsa with internal streams. */
/* 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;
return init_vgmstream_ktsr_internal(sf, true);
}
@ -89,17 +88,18 @@ static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa) {
int target_subsong = sf->stream_index;
int separate_offsets = 0;
ktsr.is_srsa = is_srsa;
if (ktsr.is_srsa) {
ktsr.base_offset = 0x10;
}
/* checks */
if (!is_id32be(0x00, sf, "KTSR"))
if (!is_id32be(ktsr.base_offset + 0x00, sf, "KTSR"))
return NULL;
if (read_u32be(0x04, sf) != 0x777B481A) /* hash id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */
if (read_u32be(ktsr.base_offset + 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"))
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.
@ -108,21 +108,28 @@ static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa) {
if (target_subsong == 0) target_subsong = 1;
ktsr.target_subsong = target_subsong;
ktsr.is_srsa = is_srsa;
if (!parse_ktsr(&ktsr, sf))
goto fail;
/* open companion body */
if (ktsr.is_external) {
const char* companion_ext = check_extensions(sf, "asbin") ? "stbin" : "ktsl2stbin";
if (ktsr.is_srsa)
companion_ext = "srst";
if (ktsr.is_srsa) {
/* try parsing TXTM if present, since .srsa+srst have hashed names and don't match unless renamed */
sf_b = read_filemap_file(sf, 0);
}
sf_b = open_streamfile_by_ext(sf, companion_ext);
if (!sf_b) {
vgm_logi("KTSR: companion file '*.%s' not found\n", companion_ext);
goto fail;
/* try (name).(ext), as seen in older games */
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);
goto fail;
}
}
}
else {
@ -539,8 +546,8 @@ static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf) {
uint32_t offset, end, name_offset;
uint32_t stream_id;
offset = 0x40;
end = get_streamfile_size(sf);
offset = 0x40 + ktsr->base_offset;
end = get_streamfile_size(sf) - ktsr->base_offset;
while (offset < end) {
uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */
uint32_t size = read_u32le(offset + 0x04, sf);
@ -582,18 +589,18 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
* up to 40: reserved
* until end: entries (totals not defined) */
ktsr->platform = read_u8(0x0b,sf);
ktsr->audio_id = read_u32le(0x0c,sf);
ktsr->platform = read_u8(ktsr->base_offset + 0x0b,sf);
ktsr->audio_id = read_u32le(ktsr->base_offset + 0x0c,sf);
if (read_u32le(0x18, sf) != read_u32le(0x1c, sf))
if (read_u32le(ktsr->base_offset + 0x18, sf) != read_u32le(ktsr->base_offset + 0x1c, sf))
goto fail;
if (read_u32le(0x1c, sf) != get_streamfile_size(sf)) {
if (read_u32le(ktsr->base_offset + 0x1c, sf) != get_streamfile_size(sf) - ktsr->base_offset) {
vgm_logi("KTSR: incorrect file size (bad rip?)\n");
goto fail;
}
offset = 0x40;
end = get_streamfile_size(sf);
offset = 0x40 + ktsr->base_offset;
end = get_streamfile_size(sf) - ktsr->base_offset;
while (offset < end) {
uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */
uint32_t size = read_u32le(offset + 0x04, sf);
@ -661,10 +668,13 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
build_name(ktsr, sf);
/* skip TSRS header */
if (ktsr->is_external && ktsr->is_srsa) {
if (ktsr->base_offset) {
//if (ktsr->is_external)
for (int i = 0; i < ktsr->channels; i++) {
ktsr->stream_offsets[i] += 0x10;
ktsr->stream_offsets[i] += ktsr->base_offset;
}
ktsr->extra_offset += ktsr->base_offset;
}
return 1;