Merge pull request #1434 from bnnm/strwav-srsa

- Fix some N1 .at9 [Labyrinth of Galleria (PC)]
- Fix .srsa names
- Fix some .str+wav [Karaoke Revolution (PS3)]
- Fix some .str+wav [Zapper Beta (PS2)]
- Add FSB key
- ktsr: add .txtm support for .srsa+srst
This commit is contained in:
bnnm 2023-10-14 20:21:57 +02:00 committed by GitHub
commit 17de674a7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 468 additions and 178 deletions

View File

@ -185,8 +185,11 @@ class TxtpInfo(object):
def _get_text(self, str):
text = self._get_string(str, full=True)
# stream names in CLI is printed as UTF-8 using '\xNN', so detect and transform
if text and '\\' in text:
return text.encode('ascii').decode('unicode-escape').encode('iso-8859-1').decode('utf-8')
try:
if text and '\\' in text:
return text.encode('ascii').decode('unicode-escape').encode('iso-8859-1').decode('utf-8')
except:
return text #odd/buggy names
return text
def _get_value(self, str):

View File

@ -1601,6 +1601,8 @@ different internally (encrypted, different versions, etc) and not always can be
- **ktsr.c**
- Koei Tecmo KTSR header [*KTSR*]
- *ktsr*: `.ktsl2asbin .asbin`
- *asrs*: `.srsa`
- *ktsr_internal*
- Subfiles: *riff ogg_vorbis ktss*
- Codecs: MSADPCM_int NGC_DSP ATRAC9
- **mups.c**
@ -1938,85 +1940,85 @@ Quick list of most codecs vgmstream supports, including many obscure ones that
are used in few games.
- PCM
- PCM 16-bit (little endian, big endian)
- PCM 8-bit (signed, unsigned, sign bit)
- PCM 4-bit (signed, unsigned)
- PCM 24-bit (little endian, big endian)
- PCM 32-bit float
- u-Law/a-LAW
- PCM 16-bit (little endian, big endian)
- PCM 8-bit (signed, unsigned, sign bit)
- PCM 4-bit (signed, unsigned)
- PCM 24-bit (little endian, big endian)
- PCM 32-bit float
- u-Law/a-LAW
- ADPCM (BRR/XA-style)
- CD-ROM XA ADPCM
- Sony PSX ADPCM a.k.a VAG (standard, badflags, configurable, extended)
- CRI ADX (standard, fixed, exponential, encrypted)
- Silicon Graphics VADPCM
- Nintendo DSP ADPCM a.k.a GC ADPCM
- Nintendo DTK ADPCM
- Nintendo AFC ADPCM
- Microsoft MSADPCM (standard, mono, Cricket Audio)
- Electronic Arts EA-XA (stereo, mono, Maxis)
- Electronic Arts EA-XAS (v0, v1)
- Konami MTAF ADPCM
- Konami MTA2 ADPCM
- FMOD FADPCM ADPCM
- Procyon Studio ADPCM
- Level-5 0x555 ADPCM
- Konami XMD ADPCM
- Argonaut ASF ADPCM
- Tantalus ADPCM
- CD-ROM XA ADPCM
- Sony PSX ADPCM a.k.a VAG (standard, badflags, configurable, extended)
- CRI ADX (standard, fixed, exponential, encrypted)
- Silicon Graphics VADPCM
- Nintendo DSP ADPCM a.k.a GC ADPCM
- Nintendo DTK ADPCM
- Nintendo AFC ADPCM
- Microsoft MSADPCM (standard, mono, Cricket Audio)
- Electronic Arts EA-XA (stereo, mono, Maxis)
- Electronic Arts EA-XAS (v0, v1)
- Konami MTAF ADPCM
- Konami MTA2 ADPCM
- FMOD FADPCM ADPCM
- Procyon Studio ADPCM
- Level-5 0x555 ADPCM
- Konami XMD ADPCM
- Argonaut ASF ADPCM
- Tantalus ADPCM
- ADPCM (IMA-style)
- DVI/IMA ADPCM (stereo/mono + high/low nibble, 3DS, Quantic Dream, SNDS, etc)
- Microsoft MS-IMA ADPCM (standard, Xbox, NDS, Radical, Wwise, FSB, WV6, etc)
- Yamaha ADPCM (AICA, Aska)
- Westwood VBR ADPCM
- OKI ADPCM (16-bit output, 4-shift, PC-FX)
- LucasArts iMUSE VBR ADPCM
- Tiger Game.com ADPCM
- DVI/IMA ADPCM (stereo/mono + high/low nibble, 3DS, Quantic Dream, SNDS, etc)
- Microsoft MS-IMA ADPCM (standard, Xbox, NDS, Radical, Wwise, FSB, WV6, etc)
- Yamaha ADPCM (AICA, Aska)
- Westwood VBR ADPCM
- OKI ADPCM (16-bit output, 4-shift, PC-FX)
- LucasArts iMUSE VBR ADPCM
- Tiger Game.com ADPCM
- ADPCM (others)
- Sony HEVAG
- Ubisoft 4/6-bit ADPCM
- Platinum ADPCM
- Paradigm MC3 ADPCM
- Ocean DSA ADPCM
- lsf ADPCM
- ITU-T G.721
- CompressWave (CWav) Huffman ADPCM
- Sony HEVAG
- Ubisoft 4/6-bit ADPCM
- Platinum ADPCM
- Paradigm MC3 ADPCM
- Ocean DSA ADPCM
- lsf ADPCM
- ITU-T G.721
- CompressWave (CWav) Huffman ADPCM
- Perceptual/transform-based
- MPEG MP1/2/3 (standard, AHX, XVAG, FSB, AWC, P3D, EA, etc)
- Xiph Vorbis (Ogg, FSB, Wwise, OGL, Silicon Knights)
- CRI HCA
- ITU-T G.722.1 annex C a.k.a. Polycom Siren 14 (Namco)
- ITU-T G.719 annex B a.k.a. Polycom Siren 22
- Xiph Opus (Ogg, Switch, EA, UE4, Exient, FSB)
- Xiph CELT (FSB)
- Microsoft XMA1/2
- Microsoft WMA v1, WMA v2, WMAPro
- AAC
- Sony ATRAC3
- Sony ATRAC3plus
- Sony ATRAC9
- Relic Codec
- tri-Ace PS2 Codec
- Bink
- AC3/SPDIF
- Musepack
- Electronic Arts EASpeex
- Electronic Arts EALayer3
- Electronic Arts EA-XMA
- Electronic Arts MicroTalk a.k.a. UTK or UMT
- Inti Creates DCT codec
- Circus XPCM VQ
- MPEG MP1/2/3 (standard, AHX, XVAG, FSB, AWC, P3D, EA, etc)
- Xiph Vorbis (Ogg, FSB, Wwise, OGL, Silicon Knights)
- CRI HCA
- ITU-T G.722.1 annex C a.k.a. Polycom Siren 14 (Namco)
- ITU-T G.719 annex B a.k.a. Polycom Siren 22
- Xiph Opus (Ogg, Switch, EA, UE4, Exient, FSB)
- Xiph CELT (FSB)
- Microsoft XMA1/2
- Microsoft WMA v1, WMA v2, WMAPro
- AAC
- Sony ATRAC3
- Sony ATRAC3plus
- Sony ATRAC9
- Relic Codec
- tri-Ace PS2 Codec
- Bink
- AC3/SPDIF
- Musepack
- Electronic Arts EASpeex
- Electronic Arts EALayer3
- Electronic Arts EA-XMA
- Electronic Arts MicroTalk a.k.a. UTK or UMT
- Inti Creates DCT codec
- Circus XPCM VQ
- Misc
- SDX2 2:1 Squareroot-Delta-Exact compression DPCM
- CBD2 2:1 Cuberoot-Delta-Exact compression DPCM
- Activision EXAKT SASSC DPCM
- Xilam DERF DPCM
- Circus XPCM DPCM
- VisualArt's NWA PCM/DPCM
- Marble WADY PCM/DPCM
- InterPlay ACM
- Inti Creates Range codec
- FLAC
- Others
- SDX2 2:1 Squareroot-Delta-Exact compression DPCM
- CBD2 2:1 Cuberoot-Delta-Exact compression DPCM
- Activision EXAKT SASSC DPCM
- Xilam DERF DPCM
- Circus XPCM DPCM
- VisualArt's NWA PCM/DPCM
- Marble WADY PCM/DPCM
- InterPlay ACM
- Inti Creates Range codec
- FLAC
- Others
Sometimes standard codecs come in non-standard layouts that aren't normally
supported by other players (like multiple `.ogg` or `.mp3` files chunked and
@ -2028,4 +2030,4 @@ proper support of encoder delay, accurate sample counts and seeking that other
plugins may lack.
Note that vgmstream doesn't (can't) reproduce in-game music 1:1, as internal
resampling, filters, volume, etc, are not replicated.
resampling, filters, volume, etc, are not replicated.

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

@ -155,6 +155,7 @@
<ClInclude Include="meta\sfh_streamfile.h" />
<ClInclude Include="meta\sqex_streamfile.h" />
<ClInclude Include="meta\sscf_encrypted.h" />
<ClInclude Include="meta\str_wav_streamfile.h" />
<ClInclude Include="meta\txth_streamfile.h" />
<ClInclude Include="meta\ubi_bao_streamfile.h" />
<ClInclude Include="meta\ubi_ckd_cwav_streamfile.h" />

View File

@ -290,6 +290,9 @@
<ClInclude Include="meta\sscf_encrypted.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\str_wav_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\txth_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>

View File

@ -72,9 +72,10 @@ static const fsbkey_info fsbkey_list[] = {
{ MODE_FSB5_STD, FSBKEY_ADD("IfYouLikeThosesSoundsWhyNotRenumerateTheir2Authors?") }, // Blanc (PC/Switch)
{ MODE_FSB5_STD, FSBKEY_ADD("L36nshM520") }, // Nishuihan Mobile (Android)
{ MODE_FSB5_STD, FSBKEY_ADD("Forza2!") }, // Forza Motorsport (PC)
{ MODE_FSB5_STD, FSBKEY_ADD("cbfjZTlUPaZI") }, // JDM: Japanese Drift Master (PC)
/* these games use a key per file, seemingly generated from the filename; could be possible to add them but there is a lot of songs,
so external .fsbkey may be better (use guessfsb 3.1 with --write-key-file) */
/* these games use a key per file, generated from the filename; could be possible to add them but there is a lot of songs,
so external .fsbkey may be better (use guessfsb 3.1 with --write-key-file or ) */
//{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero: Metallica (PC/PS3/X360) [FSB4]
//{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero: World Tour (PC/PS3/X360) [FSB4]
};

View File

@ -1,19 +1,25 @@
#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;
ktsr_codec codec;
uint32_t audio_id;
int platform;
int format;
uint32_t sound_id;
uint32_t sound_flags;
uint32_t config_flags;
int channels;
int sample_rate;
@ -39,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"))
@ -54,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);
}
@ -85,39 +88,48 @@ 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.
* Hashes are meant to be read LE, but here are BE for easier comparison. Some info from KTSR.bt */
* Hashes are meant to be read LE, but here are BE for easier comparison (they probably correspond
* to some text but are pre-hashed in exes). 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;
/* 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 {
@ -468,16 +480,53 @@ fail:
return 0;
}
/* ktsr engine reads+decrypts in the same func based on passed flag tho (reversed from exe)
* Strings are usually ASCII but Shift-JIS is used in rare cases (0x0c3e2edf.srsa) */
static void decrypt_string_ktsr(char* buf, size_t buf_size, uint32_t seed) {
for (int i = 0; i < buf_size - 1; i++) {
if (!buf[i]) /* just in case */
break;
seed = 0x343FD * seed + 0x269EC3; /* classic rand */
buf[i] ^= (seed >> 16) & 0xFF; /* 3rd byte */
if (!buf[i]) /* end null is also encrypted (but there are extra nulls after it anyway) */
break;
}
}
/* like read_string but allow any value since it can be encrypted */
static size_t read_string_ktsr(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf) {
int pos;
for (pos = 0; pos < buf_size; pos++) {
uint8_t byte = read_u8(offset + pos, sf);
char c = (char)byte;
if (buf) buf[pos] = c;
if (c == '\0')
return pos;
if (pos+1 == buf_size) {
if (buf) buf[pos] = '\0';
return buf_size;
}
}
return 0;
}
static void build_name(ktsr_header* ktsr, STREAMFILE* sf) {
char sound_name[255] = {0};
char config_name[255] = {0};
/* names can be different or same but usually config is better */
if (ktsr->sound_name_offset) {
read_string(sound_name, sizeof(sound_name), ktsr->sound_name_offset, sf);
read_string_ktsr(sound_name, sizeof(sound_name), ktsr->sound_name_offset, sf);
if (ktsr->sound_flags & 0x0008)
decrypt_string_ktsr(sound_name, sizeof(sound_name), ktsr->audio_id);
}
if (ktsr->config_name_offset) {
read_string(config_name, sizeof(config_name), ktsr->config_name_offset, sf);
read_string_ktsr(config_name, sizeof(config_name), ktsr->config_name_offset, sf);
if (ktsr->config_flags & 0x0200)
decrypt_string_ktsr(config_name, sizeof(config_name), ktsr->audio_id);
}
//if (longname[0] && shortname[0]) {
@ -493,22 +542,24 @@ static void build_name(ktsr_header* ktsr, STREAMFILE* sf) {
}
static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id) {
static void parse_longname(ktsr_header* 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;
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);
switch(type) {
case 0xBD888C36: /* config */
stream_id = read_u32be(offset + 0x08, sf);
if (stream_id != target_id)
stream_id = read_u32le(offset + 0x08, sf);
if (stream_id != ktsr->sound_id)
break;
ktsr->config_flags = read_u32le(offset + 0x0c, sf);
name_offset = read_u32le(offset + 0x28, sf);
if (name_offset > 0)
ktsr->config_name_offset = offset + name_offset;
@ -524,7 +575,7 @@ static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id
static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
uint32_t offset, end, header_offset, name_offset;
uint32_t stream_id = 0, stream_count;
uint32_t stream_count;
/* 00: KTSR
* 04: type
@ -539,15 +590,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->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);
@ -557,7 +611,7 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
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 0xA9D23BF1: /* "state container", some kind of config/floats, with names like 'State_bgm01'..N */
case 0x836FBECA: /* unknown (~0x300, encrypted? table + data) */
break;
@ -565,9 +619,10 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
ktsr->total_subsongs++;
/* sound table:
* 08: stream id (used in several places)
* 0c: unknown (low number but not version?)
* 0e: external flag
* 08: current/stream id (used in several places)
* 0c: flags (sounds only; other types are similar but different bits)
* 0x08: encrypted names (only used after ASRS was introduced?)
* 0x10000: external flag
* 10: sub-streams?
* 14: offset to header offset
* 18: offset to name
@ -576,11 +631,10 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
* --: header
* --: subheader (varies) */
if (ktsr->total_subsongs == ktsr->target_subsong) {
stream_id = read_u32be(offset + 0x08,sf);
//ktsr->is_external = read_u16le(offset + 0x0e,sf);
ktsr->sound_id = read_u32le(offset + 0x08,sf);
ktsr->sound_flags = read_u32le(offset + 0x0c,sf);
stream_count = read_u32le(offset + 0x10,sf);
if (stream_count != 1) {
VGM_LOG("ktsr: unknown stream count\n");
@ -611,14 +665,16 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
if (ktsr->target_subsong > ktsr->total_subsongs)
goto fail;
parse_longname(ktsr, sf, stream_id);
parse_longname(ktsr, sf);
build_name(ktsr, sf);
/* skip TSRS header */
if (ktsr->is_external && ktsr->is_srsa) {
/* 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] += 0x10;
ktsr->stream_offsets[i] += ktsr->base_offset;
}
ktsr->extra_offset += ktsr->base_offset; /* ? */
}
return 1;

View File

@ -74,27 +74,30 @@ fail:
return NULL;
}
#define SEGMENT_MAX 3
/* Nippon Ichi SPS wrapper (segmented) [Penny-Punching Princess (Switch), Disgaea 4 Complete (PC)] */
VGMSTREAM* init_vgmstream_sps_n1_segmented(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
off_t segment_offset;
size_t data_size, max_size;
int loop_flag, type, sample_rate;
int i, segment;
init_vgmstream_t init_vgmstream = NULL;
const char* extension;
segmented_layout_data* data = NULL;
int segment_count, loop_start_segment, loop_end_segment;
int loop_start_segment, loop_end_segment;
/* checks */
/* .at9: Penny-Punching Princess (Switch)
type = read_u32le(0x00,sf);
if (type > 10)
return NULL;
/* .at9: Penny-Punching Princess (Switch), Labyrinth of Galleria (PC)
* .nlsd: Disgaea 4 Complete (PC) */
if (!check_extensions(sf, "at9,nlsd"))
goto fail;
return NULL;
type = read_u32le(0x00,sf);
data_size = read_u32le(0x04,sf);
sample_rate = read_u16le(0x08,sf);
/* 0x0a: flag? (stereo?) */
@ -113,23 +116,58 @@ VGMSTREAM* init_vgmstream_sps_n1_segmented(STREAMFILE* sf) {
break;
default:
goto fail;
return NULL;
}
segment_offset = 0x1c;
if (data_size + segment_offset != get_streamfile_size(sf))
goto fail;
/* segmented using 3 files (intro/loop/outro). non-segmented wrapper is the same
* but with loop samples instead of sub-sizes */
uint32_t segment_offsets[SEGMENT_MAX];
uint32_t segment_sizes[SEGMENT_MAX];
uint32_t segment_start, offset;
int segment_count, segment;
if (data_size + 0x1c == get_streamfile_size(sf)) {
/* common */
segment_start = 0x1c;
offset = segment_start;
for (int i = 0; i < SEGMENT_MAX; i++) {
uint32_t segment_size = read_u32le(0x10 + 0x04*i,sf);
segment_sizes[i] = segment_size;
segment_offsets[i] = offset;
offset += segment_sizes[i];
}
}
else if (data_size + 0x18 == get_streamfile_size(sf)) {
/* Labyrinth of Galleria (PC) */
segment_start = 0x18;
offset = segment_start;
for (int i = 0; i < SEGMENT_MAX; i++) {
uint32_t next_offset;
if (i >= 2) {
next_offset = get_streamfile_size(sf) - segment_start;
}
else {
next_offset = read_u32le(0x10 + 0x04*i,sf); /* only 2 (not sure if it can be 0) */
}
segment_sizes[i] = next_offset - offset + segment_start;
segment_offsets[i] = offset;
offset += segment_sizes[i];
}
}
else {
goto fail;
}
max_size = 0;
segment_count = 0;
for (i = 0; i < 3; i++) {
size_t segment_size = read_u32le(0x10 + 0x04*i,sf);
max_size += segment_size;
/* may only set 1 segment (Disgaea4's bgm_185) */
if (segment_size)
for (int i = 0; i < SEGMENT_MAX; i++) {
/* may only set 1 segment, with empty intro/outro (Disgaea4's bgm_185) */
if (segment_sizes[i])
segment_count++;
max_size += segment_sizes[i];
}
if (data_size != max_size)
goto fail;
@ -144,25 +182,21 @@ VGMSTREAM* init_vgmstream_sps_n1_segmented(STREAMFILE* sf) {
/* open each segment subfile */
segment = 0;
for (i = 0; i < 3; i++) {
STREAMFILE* temp_sf;
size_t segment_size = read_u32le(0x10 + 0x04*i,sf);
if (!segment_size)
for (int i = 0; i < SEGMENT_MAX; i++) {
if (!segment_sizes[i])
continue;
temp_sf = setup_subfile_streamfile(sf, segment_offset,segment_size, extension);
STREAMFILE* temp_sf = setup_subfile_streamfile(sf, segment_offsets[i],segment_sizes[i], extension);
if (!temp_sf) goto fail;
data->segments[segment] = init_vgmstream(temp_sf);
close_streamfile(temp_sf);
if (!data->segments[segment]) goto fail;
segment_offset += segment_size;
segment++;
if (type == 9) {
//todo there are some trailing samples that must be removed for smooth loops, start skip seems ok
//TODO there are some trailing samples that must be removed for smooth loops, start skip seems ok
//not correct for all files, no idea how to calculate
data->segments[segment]->num_samples -= 374;
}

View File

@ -1,8 +1,10 @@
#include "meta.h"
#include "../coding/coding.h"
#include "../util/layout_utils.h"
#include "str_wav_streamfile.h"
typedef enum { PSX, DSP, XBOX, WMA, IMA, XMA2 } strwav_codec;
typedef enum { PSX, PSX_chunked, DSP, XBOX, WMA, IMA, XMA2, MPEG } strwav_codec;
typedef struct {
int tracks;
int channels;
@ -103,6 +105,29 @@ VGMSTREAM* init_vgmstream_str_wav(STREAMFILE* sf) {
vgmstream->interleave_block_size = strwav.interleave;
break;
case PSX_chunked: { /* hack */
//tracks are stereo blocks of size 0x20000 * tracks, containing 4 interleaves of 0x8000:
// | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | ...
vgmstream->coding_type = coding_PSX;
vgmstream->interleave_block_size = strwav.interleave;
for (int i = 0; i < strwav.tracks; i++) {
uint32_t chunk_size = 0x20000;
int layer_channels = 2;
STREAMFILE* temp_sf = setup_str_wav_streamfile(sf, 0x00, strwav.tracks, i, chunk_size);
if (!temp_sf) goto fail;
bool res = layered_add_sf(vgmstream, strwav.tracks, layer_channels, temp_sf);
close_streamfile(temp_sf);
if (!res)
goto fail;
}
if (!layered_add_done(vgmstream))
goto fail;
break;
}
case DSP:
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave;
@ -173,6 +198,26 @@ VGMSTREAM* init_vgmstream_str_wav(STREAMFILE* sf) {
}
#endif
#ifdef VGM_USE_MPEG
case MPEG: {
/* regular MP3 starting with ID2, stereo tracks xN (bgm + vocals) but assuming last (or only one) could be mono */
int layers = (strwav.channels + 1) / 2;
for (int i = 0; i < layers; i++) {
uint32_t size = strwav.interleave;
uint32_t offset = i * size;
const char* ext = "mp3";
int layer_channels = ((layers % 2) && i + 1 == layers) ? 1 : 2;
layered_add_subfile(vgmstream, layers, layer_channels, sf, offset, size, ext, init_vgmstream_mpeg);
}
if (!layered_add_done(vgmstream))
goto fail;
break;
}
#endif
default:
goto fail;
}
@ -435,8 +480,8 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
strwav->codec = PSX;
strwav->interleave = strwav->tracks > 1 ? 0x8000 : 0x8000;
//todo: tracks are stereo blocks of size 0x20000*tracks, containing 4 interleaves of 0x8000:
// | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | ...
if (strwav->tracks > 1) /* hack */
strwav->codec = PSX_chunked;
;VGM_LOG("STR+WAV: header ZPb (PS2)\n");
return 1;
}
@ -669,7 +714,7 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
/* 0x4c: ? (some low number) */
strwav->tracks = read_u8 (0x4e,sf_h);
/* 0x4f: 16 bps */
/* 0x54: channels per each track? (ex. 2 stereo track: 0x02,0x02) */
/* 0x54: channels per each track (ex. 2 stereo track: 0x02,0x02) */
/* 0x64: channels */
/* 0x70+: tables */
@ -712,6 +757,7 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
/* Tak and the Guardians of Gross (Wii)[2008] */
/* The House of the Dead: Overkill (Wii)[2009] (not Blitz but still the same format) */
/* All Star Karate (Wii)[2010] */
/* Karaoke Revolution (Wii)[2010] */
if ((read_u32be(0x04,sf_h) == 0x00000800 ||
read_u32be(0x04,sf_h) == 0x00000700) && /* rare? */
read_u32be(0x08,sf_h) != 0x00000000 &&
@ -730,11 +776,12 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
strwav->codec = DSP;
strwav->coefs_table = 0x7c;
strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000;
;VGM_LOG("STR+WAV: header TKGG/HOTDO/ASK (Wii)\n");
;VGM_LOG("STR+WAV: header TKGG/HOTDO/ASK/KR (Wii)\n");
return 1;
}
/* The House of the Dead: Overkill (PS3)[2009] (not Blitz but still the same format) */
/* Karaoke Revolution (PS3)[2010] */
if ((read_u32be(0x04,sf_h) == 0x00000800 ||
read_u32be(0x04,sf_h) == 0x00000700) && /* rare? */
read_u32be(0x08,sf_h) != 0x00000000 &&
@ -747,10 +794,30 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
strwav->sample_rate = read_s32be(0x38,sf_h);
strwav->flags = read_u32be(0x3c,sf_h);
strwav->channels = read_s32be(0x70,sf_h); /* tracks of 1ch */
strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000;
strwav->channels = read_s32be(0x70,sf_h);
/* other possibly-useful flags (see Karaoke Wii too):
* - 0x4b: number of tracks
* - 0x60: channels per track, ex. 020202 = 3 tracks of 2ch (max 0x08 = 8)
* - 0xa0: sizes per track (max 0x20 = 8)
* - 0xc0: samples per track (max 0x20 = 8)
* - rest: info/seek table? */
if (read_s32be(0x78,sf_h) != 0) { /* KRev */
strwav->tracks = strwav->channels / 2;
strwav->num_samples = strwav->loop_end; /* num_samples here seems to be data size */
strwav->interleave = read_s32be(0xA0,sf_h); /* one size per file, but CBR = same for all */
//C0: stream samples (same as num_samples)
strwav->codec = MPEG; /* full CBR MP3 one after other */
}
else { /* HOTD */
strwav->channels = read_s32be(0x70,sf_h); /* tracks of 1ch */
strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000;
strwav->codec = PSX;
}
strwav->codec = PSX;
;VGM_LOG("STR+WAV: header HOTDO (PS3)\n");
return 1;
}

View File

@ -0,0 +1,21 @@
#ifndef _STR_WAV_STREAMFILE_H_
#define _STR_WAV_STREAMFILE_H_
#include "deblock_streamfile.h"
/* Deblocks streams */
static STREAMFILE* setup_str_wav_streamfile(STREAMFILE* sf, off_t stream_start, int stream_count, int stream_number, size_t interleave) {
STREAMFILE *new_sf = NULL;
deblock_config_t cfg = {0};
cfg.stream_start = stream_start;
cfg.chunk_size = interleave;
cfg.step_start = stream_number;
cfg.step_count = stream_count;
/* setup sf */
new_sf = open_wrap_streamfile(sf);
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
return new_sf;
}
#endif

View File

@ -2,9 +2,17 @@
#include "../vgmstream.h"
#include "../layout/layout.h"
#include "../coding/coding.h"
bool layered_add_codec(VGMSTREAM* vs, int layers, int layer_channels) {
if (!vs || !vs->codec_data) {
typedef VGMSTREAM* (*init_vgmstream_t)(STREAMFILE*);
bool layered_add_subfile(VGMSTREAM* vs, int layers, int layer_channels, STREAMFILE* sf, uint32_t offset, uint32_t size, const char* ext, init_vgmstream_t init_vgmstream) {
int i;
layered_layout_data* data;
STREAMFILE* temp_sf;
if (!vs) {
goto fail;
}
@ -13,9 +21,54 @@ bool layered_add_codec(VGMSTREAM* vs, int layers, int layer_channels) {
if (!layers)
layers = vs->channels / layer_channels;
switch(vs->layout_type) {
case layout_segmented: //to be improved
goto fail;
case layout_layered:
data = vs->layout_data;
i = data->curr_layer;
break;
default:
data = init_layout_layered(layers);
if (!data) goto fail;
vs->layout_data = data;
vs->layout_type = layout_layered;
i = 0;
break;
}
temp_sf = setup_subfile_streamfile(sf, offset, size, ext);
if (!temp_sf) goto fail;
data->layers[i] = init_vgmstream(temp_sf);
close_streamfile(temp_sf);
if (!data->layers[i]) goto fail;
data->curr_layer++;
return true;
fail:
return false;
}
static bool layered_add_internal(VGMSTREAM* vs, int layers, int layer_channels, STREAMFILE* sf) {
int i;
layered_layout_data* data;
if (!vs) {
goto fail;
}
if (!layer_channels)
layer_channels = 1;
if (!layers)
layers = vs->channels / layer_channels;
switch(vs->layout_type) {
case layout_segmented: //to be improved
goto fail;
@ -46,9 +99,12 @@ bool layered_add_codec(VGMSTREAM* vs, int layers, int layer_channels) {
data->layers[i]->loop_end_sample = vs->loop_end_sample;
data->layers[i]->codec_data = vs->codec_data;
if (!data->layers[i]->codec_data) goto fail;
data->layers[i]->coding_type = vs->coding_type;
data->layers[i]->layout_type = layout_none;
data->layers[i]->interleave_block_size = vs->interleave_block_size;
if (vs->interleave_block_size)
data->layers[i]->layout_type = layout_interleave;
vs->codec_data = NULL; /* moved to layer, don't hold it */
@ -59,12 +115,29 @@ fail:
return false;
}
bool layered_add_sf(VGMSTREAM* vs, int layers, int layer_channels, STREAMFILE* sf) {
return layered_add_internal(vs, layers, layer_channels, sf);
}
bool layered_add_codec(VGMSTREAM* vs, int layers, int layer_channels) {
if (!vs->codec_data)
return false;
return layered_add_internal(vs, layers, layer_channels, NULL);
}
bool layered_add_done(VGMSTREAM* vs) {
//TODO: some extra checks/setup?
if (!setup_layout_layered(vs->layout_data))
goto fail;
if (!vs->coding_type) {
layered_layout_data* data = vs->layout_data;
vs->coding_type = data->layers[0]->coding_type;
}
return true;
fail:
return false;

View File

@ -3,6 +3,15 @@
#include "../vgmstream.h"
typedef VGMSTREAM* (*init_vgmstream_t)(STREAMFILE*);
/* add a new layer from subfile (setups layout if needed) */
bool layered_add_subfile(VGMSTREAM* vs, int layers, int layer_channels, STREAMFILE* sf, uint32_t offset, uint32_t size, const char* ext, init_vgmstream_t init_vgmstream);
/* add a new layer from base vgmstream (setups layout if needed) */
bool layered_add_sf(VGMSTREAM* vs, int layers, int layer_channels, STREAMFILE* sf);
/* add a new layer from codec data (setups layout if needed)
* codec is passed in the vs for easier free/etc control */
bool layered_add_codec(VGMSTREAM* vs, int layers, int layer_channels);

View File

@ -67,7 +67,7 @@ void vgm_log_set_callback(void* ctx_p, int level, int type, void* callback);
do { \
int i; \
for (i=0; i < buf_size; i++) { \
printf("%02x",buf[i]); \
printf("%02x",buf[i] & 0xFF); \
if (bytes_per_line && (i+1) % bytes_per_line == 0) printf("\n"); \
} \
printf("\n"); \

View File

@ -616,12 +616,6 @@ static VGMSTREAM* init_vgmstream_internal(STREAMFILE* sf) {
try_dual_file_stereo(vgmstream, sf, init_vgmstream_function);
}
/* clean as loops are readable metadata but loop fields may contain garbage
* (done *after* dual stereo as it needs loop fields to match) */
if (!vgmstream->loop_flag) {
vgmstream->loop_start_sample = 0;
vgmstream->loop_end_sample = 0;
}
#ifdef VGM_USE_FFMPEG
/* check FFmpeg streams here, for lack of a better place */
@ -678,6 +672,28 @@ static VGMSTREAM* init_vgmstream_internal(STREAMFILE* sf) {
void setup_vgmstream(VGMSTREAM* vgmstream) {
//TODO improve cleanup (done here to handle manually added layers)
/* sanify loops and remove bad metadata (some layouts will behave incorrectly) */
if (vgmstream->loop_flag) {
if (vgmstream->loop_end_sample <= vgmstream->loop_start_sample
|| vgmstream->loop_end_sample > vgmstream->num_samples
|| vgmstream->loop_start_sample < 0) {
VGM_LOG("VGMSTREAM: wrong loops ignored (lss=%i, lse=%i, ns=%i)\n",
vgmstream->loop_start_sample, vgmstream->loop_end_sample, vgmstream->num_samples);
vgmstream->loop_flag = 0;
vgmstream->loop_start_sample = 0;
vgmstream->loop_end_sample = 0;
}
}
/* clean as loops are readable metadata but loop fields may contain garbage
* (done *after* dual stereo as it needs loop fields to match) */
if (!vgmstream->loop_flag) {
vgmstream->loop_start_sample = 0;
vgmstream->loop_end_sample = 0;
}
/* save start things so we can restart when seeking */
memcpy(vgmstream->start_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels);
memcpy(vgmstream->start_vgmstream, vgmstream, sizeof(VGMSTREAM));