Add .k2sb [Three Kingdoms HEROES (macOS)]

This commit is contained in:
bnnm 2025-01-19 01:48:32 +01:00
parent 45ed922338
commit 095d6f39fb
6 changed files with 170 additions and 96 deletions

View File

@ -264,10 +264,11 @@ different internally (encrypted, different versions, etc) and not always can be
- Codecs: PSX
- **riff.c**
- RIFF WAVE header [*RIFF_WAVE*]
- RIFF WAVE header (labl looping) [*RIFF_WAVE_labl*]
- RIFF WAVE header (smpl looping) [*RIFF_WAVE_smpl*]
- RIFF WAVE header (cue looping) [*RIFF_WAVE_cue*]
- RIFF WAVE header (labl looping) [*RIFF_WAVE_labl*]
- RIFF WAVE header (ctrl looping) [*RIFF_WAVE_ctrl*]
- RIFF WAVE header (wsmp looping) [*RIFF_WAVE_wsmp*]
- RIFF WAVE header (ctrl looping) [*RIFF_WAVE_MWV*]
- RIFX WAVE header [*RIFX_WAVE*]
- 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 .wma .lwma .caf .wax .voi .se .v`
@ -1075,7 +1076,7 @@ different internally (encrypted, different versions, etc) and not always can be
- Rockstar AWC header [*AWC*]
- *awc*: `.awc`
- Subfiles: *riff*
- Codecs: PCM16BE PCM16LE AWC_IMA XMA2 MPEG_custom MPEG VORBIS_custom ATRAC9 NGC_DSP
- Codecs: PCM16BE PCM16LE AWC_IMA XMA2 MPEG_custom MPEG VORBIS_custom ATRAC9 NGC_DSP Opus
- **opus.c**
- Nintendo Switch OPUS header [*OPUS*]
- *opus_std*: `.opus .lopus .bgm .opu .ogg .logg + .psi`
@ -1115,6 +1116,10 @@ different internally (encrypted, different versions, etc) and not always can be
- *ubi_sb_sequence*: `(base) + .(external)`
- *ubi_sb_header*: `(base) + .(external) .kat`
- Codecs: PCM16LE AICA_int UBI_IMA UBI_SCE_IMA UBI_ADPCM PSX XBOX_IMA NGC_DSP ATRAC3 XMA OGG_VORBIS DVI_IMA_int
- **ubi_apm.c**
- Ubisoft APM header [*UBI_APM*]
- *ubi_apm*: `.apm`
- Codecs: DVI_IMA_int
- **ezw.c**
- EZ2DJ EZWAVE header [*EZW*]
- *ezw*: `.ezw`
@ -1195,7 +1200,7 @@ different internally (encrypted, different versions, etc) and not always can be
- **wave.c**
- EngineBlack .WAVE header [*WAVE*]
- *wave*: `.wave`
- Codecs: IMA_int NGC_DSP
- Codecs: PCM8 PCM16BE NGC_DSP IMA_int
- **wave_segmented.c**
- EngineBlack .WAVE header [*WAVE*]
- EngineBlack .WAVE header (segmented) [*WAVE_segmented*]
@ -1598,9 +1603,10 @@ different internally (encrypted, different versions, etc) and not always can be
- Koei Tecmo KTSR header [*KTSR*]
- *ktsr*: `.ktsl2asbin .asbin`
- *asrs*: `.srsa`
- *sdbs*: `.k2sb`
- *ktsr_internal*
- Subfiles: *riff ogg_vorbis ktss ktac*
- Codecs: MSADPCM_mono NGC_DSP ATRAC9
- Subfiles: *riff ogg_vorbis ktss ktac ka1a kma9*
- Codecs: MSADPCM_mono KA1A NGC_DSP ATRAC9
- **mups.c**
- (container)
- *mups*: `.mups .(extensionless)`
@ -1816,6 +1822,30 @@ different internally (encrypted, different versions, etc) and not always can be
- Ongakukan RIFF WAVE header [*ONGAKUKAN_RIFF_ADP*]
- *adp_ongakukan*: `.adp`
- Codecs: ONGAKUKAN_ADPCM
- **sdd.c**
- Doki Denki DSBH header [*SDD*]
- *sdd*: `.sdd`
- Codecs: NGC_DSP PCM16LE PSX
- **ka1a.c**
- Koei Tecmo KA1A header [*KA1A*]
- *ka1a*: `.ka1a`
- Codecs: KA1A
- **hd_bd.c**
- Sony HD+BD header [*HD_BD*]
- *hd_bd*: `.hd .hbd + .bd`
- Codecs: PSX
- **pphd.c**
- Sony PPHD header [*PPHD*]
- *pphd*: `.phd + .pbd`
- Codecs: PSX
- **xabp.c**
- cavia XABp header [*XABP*]
- *xabp*: `.hd2 + .bd`
- Codecs: PSX
- **i3ds.c**
- Codemasters i3DS header [*I3DS*]
- *i3ds*: `.3ds`
- Codecs: NGC_DSP
- **agsc.c**
- Retro Studios AGSC header [*AGSC*]
- *agsc*: `.agsc`

View File

@ -274,6 +274,7 @@ static const char* extension_list[] = {
"joe",
"jstm",
"k2sb",
"ka1a",
"kat",
"kces",

View File

@ -9,8 +9,18 @@ typedef enum { NONE, MSADPCM, DSP, GCADPCM, ATRAC9, RIFF_ATRAC9, KMA9, AT9_KM9,
#define MAX_CHANNELS 8
typedef struct {
uint32_t base_offset;
bool is_srsa;
bool is_sdbs;
uint32_t as_offset;
uint32_t as_size;
uint32_t st_offset;
uint32_t st_size;
} ktsr_meta_t;
typedef struct {
uint32_t as_offset;
uint32_t as_size;
int total_subsongs;
int target_subsong;
ktsr_codec codec;
@ -37,12 +47,12 @@ typedef struct {
uint32_t sound_name_offset;
uint32_t config_name_offset;
char name[255+1];
} ktsr_header;
} ktsr_header_t;
static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa);
static bool 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);
static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, ktsr_meta_t* info);
static bool parse_ktsr(ktsr_header_t* ktsr, STREAMFILE* sf);
static layered_layout_data* build_layered_atrac9(ktsr_header_t* ktsr, STREAMFILE *sf, uint32_t config_data);
static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, uint32_t st_offset, ktsr_header_t* ktsr, VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf), const char* ext);
/* KTSR - Koei Tecmo sound resource container */
VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
@ -57,7 +67,10 @@ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
if (!check_extensions(sf, "ktsl2asbin,asbin"))
return NULL;
return init_vgmstream_ktsr_internal(sf, false) ;
ktsr_meta_t info = {
.as_size = get_streamfile_size(sf),
};
return init_vgmstream_ktsr_internal(sf, &info);
}
/* ASRS - container of KTSR found in newer games */
@ -70,7 +83,7 @@ VGMSTREAM* init_vgmstream_asrs(STREAMFILE* sf) {
/* 0x08: file size */
/* 0x0c: null */
/* .srsa: header id (as generated by common tools, probably "(something)asbin") */
/* .srsa: header id and 'class' in hashed names */
if (!check_extensions(sf, "srsa"))
return NULL;
@ -78,29 +91,100 @@ VGMSTREAM* init_vgmstream_asrs(STREAMFILE* sf) {
* .srsa/srst usually have hashed filenames, so it isn't easy to match them, so this
* is mainly useful for .srsa with internal streams. */
return init_vgmstream_ktsr_internal(sf, true);
ktsr_meta_t info = {
.is_srsa = true,
.as_offset = 0x10,
.as_size = get_streamfile_size(sf) - 0x10,
.st_offset = 0x10,
};
return init_vgmstream_ktsr_internal(sf, &info);
}
/* sdbs - container of KTSR found in newer games */
VGMSTREAM* init_vgmstream_sdbs(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00, sf, "sdbs"))
return NULL;
// .srsa: actual extension
if (!check_extensions(sf, "k2sb"))
return NULL;
// mini-container of memory + stream KTSR
ktsr_meta_t info = {
.is_sdbs = true,
.as_offset = read_u32le(0x04, sf),
.as_size = read_u32le(0x08, sf),
.st_offset = read_u32le(0x0c, sf),
.st_size = read_u32le(0x10, sf),
};
return init_vgmstream_ktsr_internal(sf, &info);
}
static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* sf_b = NULL;
ktsr_header ktsr = {0};
int target_subsong = sf->stream_index;
bool separate_offsets = false;
static STREAMFILE* setup_sf_body(STREAMFILE* sf, ktsr_header_t* ktsr, ktsr_meta_t* info) {
ktsr.is_srsa = is_srsa;
if (ktsr.is_srsa) {
ktsr.base_offset = 0x10;
// use current
if (!ktsr->is_external)
return sf;
// skip extra header (internals are pre-adjusted) */
if (ktsr->is_external && info->st_offset) {
for (int i = 0; i < ktsr->channels; i++) {
ktsr->stream_offsets[i] += info->st_offset;
}
//ktsr->extra_offset += ktsr->st_offset; // ?
}
if (info->is_sdbs) {
// .k2sb have data pasted together
return sf;
}
/* open companion body */
STREAMFILE* sf_b = NULL;
if (info->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);
}
if (!sf_b) {
// try (name).(ext), as seen in older games
const char* companion_ext = check_extensions(sf, "asbin") ? "stbin" : "ktsl2stbin";
if (info->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);
return NULL;
}
}
return sf_b;
}
static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, ktsr_meta_t* info) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* sf_b = NULL;
ktsr_header_t ktsr = {0};
ktsr.as_offset = info->as_offset;
ktsr.as_size = info->as_size;
/* checks */
if (!is_id32be(ktsr.base_offset + 0x00, sf, "KTSR"))
if (!is_id32be(ktsr.as_offset + 0x00, sf, "KTSR"))
return NULL;
if (read_u32be(ktsr.base_offset + 0x04, sf) != 0x777B481A) /* hash id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */
if (read_u32be(ktsr.as_offset + 0x04, sf) != 0x777B481A) /* hash id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */
return NULL;
bool separate_offsets = false;
int target_subsong = sf->stream_index;
/* KTSR can be a memory file (ktsl2asbin), streams (ktsl2stbin) and global config (ktsl2gcbin)
* This accepts .ktsl2asbin with internal data or external streams as subsongs.
@ -111,37 +195,15 @@ static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa) {
ktsr.target_subsong = target_subsong;
if (!parse_ktsr(&ktsr, sf))
goto fail;
return NULL;
if (ktsr.total_subsongs == 0) {
vgm_logi("KTSR: file has no subsongs\n");
return NULL;
}
/* open companion body */
if (ktsr.is_external) {
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);
}
if (!sf_b) {
/* 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 {
sf_b = sf;
}
sf_b = setup_sf_body(sf, &ktsr, info);
/* subfiles */
{
@ -166,7 +228,7 @@ static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa) {
}
if (init_vgmstream) {
vgmstream = init_vgmstream_ktsr_sub(sf_b, &ktsr, init_vgmstream, ext);
vgmstream = init_vgmstream_ktsr_sub(sf_b, info->st_offset, &ktsr, init_vgmstream, ext);
if (!vgmstream) goto fail;
if (sf_b != sf) close_streamfile(sf_b);
@ -253,8 +315,7 @@ static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa) {
/* data offset per channel is absolute (not actual interleave since there is padding) in some cases */
if (separate_offsets) {
int i;
for (i = 0; i < ktsr.channels; i++) {
for (int i = 0; i < ktsr.channels; i++) {
vgmstream->ch[i].offset = ktsr.stream_offsets[i];
}
}
@ -269,11 +330,11 @@ fail:
}
// TODO improve, unify with other metas that do similar stuff
static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, ktsr_header* ktsr, VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf), const char* ext) {
static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, uint32_t st_offset, ktsr_header_t* ktsr, VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf), const char* ext) {
VGMSTREAM* sub_vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
temp_sf = setup_ktsr_streamfile(sf_b, ktsr->is_external, ktsr->stream_offsets[0], ktsr->stream_sizes[0], ext);
temp_sf = setup_ktsr_streamfile(sf_b, st_offset, ktsr->is_external, ktsr->stream_offsets[0], ktsr->stream_sizes[0], ext);
if (!temp_sf) return NULL;
sub_vgmstream = init_vgmstream(temp_sf);
@ -293,18 +354,17 @@ static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, ktsr_header* ktsr, V
}
static layered_layout_data* build_layered_atrac9(ktsr_header* ktsr, STREAMFILE* sf, uint32_t config_data) {
static layered_layout_data* build_layered_atrac9(ktsr_header_t* ktsr, STREAMFILE* sf, uint32_t config_data) {
STREAMFILE* temp_sf = NULL;
layered_layout_data* data = NULL;
int layers = ktsr->channels;
int i;
/* init layout */
data = init_layout_layered(layers);
if (!data) goto fail;
for (i = 0; i < layers; i++) {
for (int i = 0; i < layers; i++) {
data->layers[i] = allocate_vgmstream(1, 0);
if (!data->layers[i]) goto fail;
@ -349,7 +409,7 @@ fail:
}
static int parse_codec(ktsr_header* ktsr) {
static int parse_codec(ktsr_header_t* ktsr) {
/* platform + format to codec, simplified until more codec combos are found */
switch(ktsr->platform) {
@ -399,12 +459,10 @@ fail:
return 0;
}
static bool parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, uint32_t offset) {
static bool parse_ktsr_subfile(ktsr_header_t* ktsr, STREAMFILE* sf, uint32_t offset) {
uint32_t suboffset, starts_offset, sizes_offset;
int i;
uint32_t type;
type = read_u32be(offset + 0x00, sf); /* hash-id? */
uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */
//size = read_u32le(offset + 0x04, sf);
// probably could check the flags in sound header, but the format is kinda messy
@ -496,7 +554,7 @@ static bool parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, uint32_t offse
starts_offset = read_u32le(suboffset + 0x00, sf) + offset;
sizes_offset = read_u32le(suboffset + 0x04, sf) + offset;
for (i = 0; i < ktsr->channels; i++) {
for (int i = 0; i < ktsr->channels; i++) {
ktsr->stream_offsets[i] = read_u32le(starts_offset + 0x04*i, sf) + offset;
ktsr->stream_sizes[i] = read_u32le(sizes_offset + 0x04*i, sf);
}
@ -513,7 +571,6 @@ static bool parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, uint32_t offse
if (!parse_codec(ktsr))
goto fail;
return true;
fail:
VGM_LOG("ktsr: error parsing subheader\n");
@ -553,7 +610,7 @@ static size_t read_string_ktsr(char* buf, size_t buf_size, off_t offset, STREAMF
return 0;
}
static void build_name(ktsr_header* ktsr, STREAMFILE* sf) {
static void build_name(ktsr_header_t* ktsr, STREAMFILE* sf) {
char sound_name[255] = {0};
char config_name[255] = {0};
@ -583,13 +640,13 @@ static void build_name(ktsr_header* ktsr, STREAMFILE* sf) {
}
static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf) {
static void parse_longname(ktsr_header_t* ktsr, STREAMFILE* sf) {
/* more configs than sounds is possible so we need target_id first */
uint32_t offset, end, name_offset;
uint32_t stream_id;
offset = 0x40 + ktsr->base_offset;
end = get_streamfile_size(sf) - ktsr->base_offset;
offset = 0x40 + ktsr->as_offset;
end = ktsr->as_offset + ktsr->as_size;
while (offset < end) {
uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */
uint32_t size = read_u32le(offset + 0x04, sf);
@ -614,7 +671,7 @@ static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf) {
}
}
static bool parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
static bool parse_ktsr(ktsr_header_t* ktsr, STREAMFILE* sf) {
uint32_t offset, end, header_offset, name_offset;
uint32_t stream_count;
@ -631,18 +688,18 @@ static bool parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
* up to 40: reserved
* until end: entries (totals not defined) */
ktsr->platform = read_u8(ktsr->base_offset + 0x0b,sf);
ktsr->audio_id = read_u32le(ktsr->base_offset + 0x0c,sf);
ktsr->platform = read_u8 (ktsr->as_offset + 0x0b,sf);
ktsr->audio_id = read_u32le(ktsr->as_offset + 0x0c,sf);
if (read_u32le(ktsr->base_offset + 0x18, sf) != read_u32le(ktsr->base_offset + 0x1c, sf))
if (read_u32le(ktsr->as_offset + 0x18, sf) != read_u32le(ktsr->as_offset + 0x1c, sf))
goto fail;
if (read_u32le(ktsr->base_offset + 0x1c, sf) != get_streamfile_size(sf) - ktsr->base_offset) {
if (read_u32le(ktsr->as_offset + 0x1c, sf) != ktsr->as_size) {
vgm_logi("KTSR: incorrect file size (bad rip?)\n");
goto fail;
}
offset = 0x40 + ktsr->base_offset;
end = get_streamfile_size(sf) - ktsr->base_offset;
offset = 0x40 + ktsr->as_offset;
end = ktsr->as_offset + ktsr->as_size;
while (offset < end) {
uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */
uint32_t size = read_u32le(offset + 0x04, sf);
@ -714,15 +771,6 @@ static bool parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
parse_longname(ktsr, sf);
build_name(ktsr, sf);
/* skip TSRS header (internals are pre-adjusted) */
if (ktsr->is_external && ktsr->base_offset) {
for (int i = 0; i < ktsr->channels; i++) {
ktsr->stream_offsets[i] += ktsr->base_offset;
}
ktsr->extra_offset += ktsr->base_offset; /* ? */
}
return true;
fail:
vgm_logi("KTSR: unknown variation (report)\n");

View File

@ -119,18 +119,14 @@ static size_t ktsr_io_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t l
/* Decrypts blowfish KTSR streams */
static STREAMFILE* setup_ktsr_streamfile(STREAMFILE* sf, bool is_external, uint32_t subfile_offset, uint32_t subfile_size, const char* extension) {
static STREAMFILE* setup_ktsr_streamfile(STREAMFILE* sf, uint32_t st_offset, bool is_external, uint32_t subfile_offset, uint32_t subfile_size, const char* extension) {
STREAMFILE* new_sf = NULL;
ktsr_io_data io_data = {0};
if (is_external) {
uint32_t offset = 0x00;
if (is_id32be(0x00, sf, "TSRS"))
offset += 0x10;
if (!is_id32be(offset + 0x00, sf, "KTSR"))
goto fail;
read_streamfile(io_data.key, offset + 0x20, sizeof(io_data.key), sf);
if (!is_id32be(st_offset + 0x00, sf, "KTSR"))
return NULL;
read_streamfile(io_data.key, st_offset + 0x20, sizeof(io_data.key), sf);
}
/* setup subfile */
@ -143,10 +139,7 @@ static STREAMFILE* setup_ktsr_streamfile(STREAMFILE* sf, bool is_external, uint3
new_sf = open_clamp_streamfile_f(new_sf, subfile_offset, subfile_size);
if (extension)
new_sf = open_fakename_streamfile_f(new_sf, NULL, extension);
return new_sf;
fail:
return NULL;
}
#endif

View File

@ -898,6 +898,7 @@ VGMSTREAM* init_vgmstream_imuse(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_asrs(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_sdbs(STREAMFILE* sf);
VGMSTREAM* init_vgmstream_mups(STREAMFILE* sf);

View File

@ -515,6 +515,7 @@ init_vgmstream_t init_vgmstream_functions[] = {
init_vgmstream_pphd,
init_vgmstream_xabp,
init_vgmstream_i3ds,
init_vgmstream_sdbs,
/* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */
init_vgmstream_agsc,