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): def _get_text(self, str):
text = self._get_string(str, full=True) text = self._get_string(str, full=True)
# stream names in CLI is printed as UTF-8 using '\xNN', so detect and transform # stream names in CLI is printed as UTF-8 using '\xNN', so detect and transform
if text and '\\' in text: try:
return text.encode('ascii').decode('unicode-escape').encode('iso-8859-1').decode('utf-8') 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 return text
def _get_value(self, str): def _get_value(self, str):

View File

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

View File

@ -290,6 +290,9 @@
<ClInclude Include="meta\sscf_encrypted.h"> <ClInclude Include="meta\sscf_encrypted.h">
<Filter>meta\Header Files</Filter> <Filter>meta\Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="meta\str_wav_streamfile.h">
<Filter>meta\Header Files</Filter>
</ClInclude>
<ClInclude Include="meta\txth_streamfile.h"> <ClInclude Include="meta\txth_streamfile.h">
<Filter>meta\Header Files</Filter> <Filter>meta\Header Files</Filter>
</ClInclude> </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("IfYouLikeThosesSoundsWhyNotRenumerateTheir2Authors?") }, // Blanc (PC/Switch)
{ MODE_FSB5_STD, FSBKEY_ADD("L36nshM520") }, // Nishuihan Mobile (Android) { MODE_FSB5_STD, FSBKEY_ADD("L36nshM520") }, // Nishuihan Mobile (Android)
{ MODE_FSB5_STD, FSBKEY_ADD("Forza2!") }, // Forza Motorsport (PC) { 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, /* 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) */ 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: Metallica (PC/PS3/X360) [FSB4]
//{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero: World Tour (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 "meta.h"
#include "../coding/coding.h" #include "../coding/coding.h"
#include "../layout/layout.h" #include "../layout/layout.h"
#include "../util/companion_files.h"
typedef enum { NONE, MSADPCM, DSP, GCADPCM, ATRAC9, RIFF_ATRAC9, KOVS, KTSS, } ktsr_codec; typedef enum { NONE, MSADPCM, DSP, GCADPCM, ATRAC9, RIFF_ATRAC9, KOVS, KTSS, } ktsr_codec;
#define MAX_CHANNELS 8 #define MAX_CHANNELS 8
typedef struct { typedef struct {
uint32_t base_offset;
bool is_srsa; bool is_srsa;
int total_subsongs; int total_subsongs;
int target_subsong; int target_subsong;
ktsr_codec codec; ktsr_codec codec;
uint32_t audio_id;
int platform; int platform;
int format; int format;
uint32_t sound_id;
uint32_t sound_flags;
uint32_t config_flags;
int channels; int channels;
int sample_rate; 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 */ /* KTSR - Koei Tecmo sound resource container */
VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) { 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) ; return init_vgmstream_ktsr_internal(sf, false) ;
} }
/* ASRS - container of KTSR found in newer games */ /* ASRS - container of KTSR found in newer games */
VGMSTREAM* init_vgmstream_asrs(STREAMFILE* sf) { VGMSTREAM* init_vgmstream_asrs(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
/* checks */ /* checks */
if (!is_id32be(0x00, sf, "ASRS")) if (!is_id32be(0x00, sf, "ASRS"))
@ -54,27 +69,15 @@ VGMSTREAM* init_vgmstream_asrs(STREAMFILE* sf) {
/* 0x08: file size */ /* 0x08: file size */
/* 0x0c: null */ /* 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")) if (!check_extensions(sf, "srsa"))
return NULL; return NULL;
/* mini-container of memory-KTSR (streams also have a "TSRS" for stream-KTSR). /* 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 * .srsa/srst usually have hashed filenames, so it isn't easy to match them, so this
* for .srsa with internal streams. */ * 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 */ return init_vgmstream_ktsr_internal(sf, true);
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;
} }
@ -85,39 +88,48 @@ static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa) {
int target_subsong = sf->stream_index; int target_subsong = sf->stream_index;
int separate_offsets = 0; int separate_offsets = 0;
ktsr.is_srsa = is_srsa;
if (ktsr.is_srsa) {
ktsr.base_offset = 0x10;
}
/* checks */ /* checks */
if (!is_id32be(0x00, sf, "KTSR")) if (!is_id32be(ktsr.base_offset + 0x00, sf, "KTSR"))
return NULL; 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; 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) /* KTSR can be a memory file (ktsl2asbin), streams (ktsl2stbin) and global config (ktsl2gcbin)
* This accepts .ktsl2asbin with internal data or external streams as subsongs. * 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; if (target_subsong == 0) target_subsong = 1;
ktsr.target_subsong = target_subsong; ktsr.target_subsong = target_subsong;
ktsr.is_srsa = is_srsa;
if (!parse_ktsr(&ktsr, sf)) if (!parse_ktsr(&ktsr, sf))
goto fail; goto fail;
/* open companion body */ /* open companion body */
if (ktsr.is_external) { if (ktsr.is_external) {
const char* companion_ext = check_extensions(sf, "asbin") ? "stbin" : "ktsl2stbin"; if (ktsr.is_srsa) {
if (ktsr.is_srsa) /* try parsing TXTM if present, since .srsa+srst have hashed names and don't match unless renamed */
companion_ext = "srst"; sf_b = read_filemap_file(sf, 0);
}
sf_b = open_streamfile_by_ext(sf, companion_ext);
if (!sf_b) { if (!sf_b) {
vgm_logi("KTSR: companion file '*.%s' not found\n", companion_ext); /* try (name).(ext), as seen in older games */
goto fail; 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 { else {
@ -468,16 +480,53 @@ fail:
return 0; 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) { static void build_name(ktsr_header* ktsr, STREAMFILE* sf) {
char sound_name[255] = {0}; char sound_name[255] = {0};
char config_name[255] = {0}; char config_name[255] = {0};
/* names can be different or same but usually config is better */ /* names can be different or same but usually config is better */
if (ktsr->sound_name_offset) { 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) { 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]) { //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 */ /* more configs than sounds is possible so we need target_id first */
uint32_t offset, end, name_offset; uint32_t offset, end, name_offset;
uint32_t stream_id; uint32_t stream_id;
offset = 0x40; offset = 0x40 + ktsr->base_offset;
end = get_streamfile_size(sf); end = get_streamfile_size(sf) - ktsr->base_offset;
while (offset < end) { while (offset < end) {
uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */ uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */
uint32_t size = read_u32le(offset + 0x04, sf); uint32_t size = read_u32le(offset + 0x04, sf);
switch(type) { switch(type) {
case 0xBD888C36: /* config */ case 0xBD888C36: /* config */
stream_id = read_u32be(offset + 0x08, sf); stream_id = read_u32le(offset + 0x08, sf);
if (stream_id != target_id) if (stream_id != ktsr->sound_id)
break; break;
ktsr->config_flags = read_u32le(offset + 0x0c, sf);
name_offset = read_u32le(offset + 0x28, sf); name_offset = read_u32le(offset + 0x28, sf);
if (name_offset > 0) if (name_offset > 0)
ktsr->config_name_offset = offset + name_offset; 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) { static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
uint32_t offset, end, header_offset, name_offset; uint32_t offset, end, header_offset, name_offset;
uint32_t stream_id = 0, stream_count; uint32_t stream_count;
/* 00: KTSR /* 00: KTSR
* 04: type * 04: type
@ -539,15 +590,18 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
* up to 40: reserved * up to 40: reserved
* until end: entries (totals not defined) */ * 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; 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; goto fail;
}
offset = 0x40; offset = 0x40 + ktsr->base_offset;
end = get_streamfile_size(sf); end = get_streamfile_size(sf) - ktsr->base_offset;
while (offset < end) { while (offset < end) {
uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */ uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */
uint32_t size = read_u32le(offset + 0x04, sf); 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 0x6172DBA8: /* ? (mostly empty) */
case 0xBD888C36: /* cue? (floats, stream id, etc, may have extended name; can have sub-chunks)-appears N times */ 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 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) */ case 0x836FBECA: /* unknown (~0x300, encrypted? table + data) */
break; break;
@ -565,9 +619,10 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
ktsr->total_subsongs++; ktsr->total_subsongs++;
/* sound table: /* sound table:
* 08: stream id (used in several places) * 08: current/stream id (used in several places)
* 0c: unknown (low number but not version?) * 0c: flags (sounds only; other types are similar but different bits)
* 0e: external flag * 0x08: encrypted names (only used after ASRS was introduced?)
* 0x10000: external flag
* 10: sub-streams? * 10: sub-streams?
* 14: offset to header offset * 14: offset to header offset
* 18: offset to name * 18: offset to name
@ -576,11 +631,10 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
* --: header * --: header
* --: subheader (varies) */ * --: subheader (varies) */
if (ktsr->total_subsongs == ktsr->target_subsong) { if (ktsr->total_subsongs == ktsr->target_subsong) {
stream_id = read_u32be(offset + 0x08,sf); ktsr->sound_id = read_u32le(offset + 0x08,sf);
//ktsr->is_external = read_u16le(offset + 0x0e,sf); ktsr->sound_flags = read_u32le(offset + 0x0c,sf);
stream_count = read_u32le(offset + 0x10,sf); stream_count = read_u32le(offset + 0x10,sf);
if (stream_count != 1) { if (stream_count != 1) {
VGM_LOG("ktsr: unknown stream count\n"); 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) if (ktsr->target_subsong > ktsr->total_subsongs)
goto fail; goto fail;
parse_longname(ktsr, sf, stream_id); parse_longname(ktsr, sf);
build_name(ktsr, sf); build_name(ktsr, sf);
/* skip TSRS header */ /* skip TSRS header (internals are pre-adjusted) */
if (ktsr->is_external && ktsr->is_srsa) { if (ktsr->is_external && ktsr->base_offset) {
for (int i = 0; i < ktsr->channels; i++) { 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; return 1;

View File

@ -74,27 +74,30 @@ fail:
return NULL; return NULL;
} }
#define SEGMENT_MAX 3
/* Nippon Ichi SPS wrapper (segmented) [Penny-Punching Princess (Switch), Disgaea 4 Complete (PC)] */ /* Nippon Ichi SPS wrapper (segmented) [Penny-Punching Princess (Switch), Disgaea 4 Complete (PC)] */
VGMSTREAM* init_vgmstream_sps_n1_segmented(STREAMFILE* sf) { VGMSTREAM* init_vgmstream_sps_n1_segmented(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
off_t segment_offset;
size_t data_size, max_size; size_t data_size, max_size;
int loop_flag, type, sample_rate; int loop_flag, type, sample_rate;
int i, segment;
init_vgmstream_t init_vgmstream = NULL; init_vgmstream_t init_vgmstream = NULL;
const char* extension; const char* extension;
segmented_layout_data* data = NULL; segmented_layout_data* data = NULL;
int segment_count, loop_start_segment, loop_end_segment; int loop_start_segment, loop_end_segment;
/* checks */ /* 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) */ * .nlsd: Disgaea 4 Complete (PC) */
if (!check_extensions(sf, "at9,nlsd")) if (!check_extensions(sf, "at9,nlsd"))
goto fail; return NULL;
type = read_u32le(0x00,sf);
data_size = read_u32le(0x04,sf); data_size = read_u32le(0x04,sf);
sample_rate = read_u16le(0x08,sf); sample_rate = read_u16le(0x08,sf);
/* 0x0a: flag? (stereo?) */ /* 0x0a: flag? (stereo?) */
@ -113,23 +116,58 @@ VGMSTREAM* init_vgmstream_sps_n1_segmented(STREAMFILE* sf) {
break; break;
default: 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 /* segmented using 3 files (intro/loop/outro). non-segmented wrapper is the same
* but with loop samples instead of sub-sizes */ * 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; max_size = 0;
segment_count = 0; segment_count = 0;
for (i = 0; i < 3; i++) { for (int i = 0; i < SEGMENT_MAX; i++) {
size_t segment_size = read_u32le(0x10 + 0x04*i,sf); /* may only set 1 segment, with empty intro/outro (Disgaea4's bgm_185) */
max_size += segment_size; if (segment_sizes[i])
/* may only set 1 segment (Disgaea4's bgm_185) */
if (segment_size)
segment_count++; segment_count++;
max_size += segment_sizes[i];
} }
if (data_size != max_size) if (data_size != max_size)
goto fail; goto fail;
@ -144,25 +182,21 @@ VGMSTREAM* init_vgmstream_sps_n1_segmented(STREAMFILE* sf) {
/* open each segment subfile */ /* open each segment subfile */
segment = 0; segment = 0;
for (i = 0; i < 3; i++) { for (int i = 0; i < SEGMENT_MAX; i++) {
STREAMFILE* temp_sf; if (!segment_sizes[i])
size_t segment_size = read_u32le(0x10 + 0x04*i,sf);
if (!segment_size)
continue; 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; if (!temp_sf) goto fail;
data->segments[segment] = init_vgmstream(temp_sf); data->segments[segment] = init_vgmstream(temp_sf);
close_streamfile(temp_sf); close_streamfile(temp_sf);
if (!data->segments[segment]) goto fail; if (!data->segments[segment]) goto fail;
segment_offset += segment_size;
segment++; segment++;
if (type == 9) { 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 //not correct for all files, no idea how to calculate
data->segments[segment]->num_samples -= 374; data->segments[segment]->num_samples -= 374;
} }

View File

@ -1,8 +1,10 @@
#include "meta.h" #include "meta.h"
#include "../coding/coding.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 { typedef struct {
int tracks; int tracks;
int channels; int channels;
@ -103,6 +105,29 @@ VGMSTREAM* init_vgmstream_str_wav(STREAMFILE* sf) {
vgmstream->interleave_block_size = strwav.interleave; vgmstream->interleave_block_size = strwav.interleave;
break; 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: case DSP:
vgmstream->coding_type = coding_NGC_DSP; vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave; vgmstream->layout_type = layout_interleave;
@ -173,6 +198,26 @@ VGMSTREAM* init_vgmstream_str_wav(STREAMFILE* sf) {
} }
#endif #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: default:
goto fail; goto fail;
} }
@ -435,8 +480,8 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
strwav->codec = PSX; strwav->codec = PSX;
strwav->interleave = strwav->tracks > 1 ? 0x8000 : 0x8000; strwav->interleave = strwav->tracks > 1 ? 0x8000 : 0x8000;
//todo: tracks are stereo blocks of size 0x20000*tracks, containing 4 interleaves of 0x8000: if (strwav->tracks > 1) /* hack */
// | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | ... strwav->codec = PSX_chunked;
;VGM_LOG("STR+WAV: header ZPb (PS2)\n"); ;VGM_LOG("STR+WAV: header ZPb (PS2)\n");
return 1; return 1;
} }
@ -669,7 +714,7 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
/* 0x4c: ? (some low number) */ /* 0x4c: ? (some low number) */
strwav->tracks = read_u8 (0x4e,sf_h); strwav->tracks = read_u8 (0x4e,sf_h);
/* 0x4f: 16 bps */ /* 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 */ /* 0x64: channels */
/* 0x70+: tables */ /* 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] */ /* Tak and the Guardians of Gross (Wii)[2008] */
/* The House of the Dead: Overkill (Wii)[2009] (not Blitz but still the same format) */ /* The House of the Dead: Overkill (Wii)[2009] (not Blitz but still the same format) */
/* All Star Karate (Wii)[2010] */ /* All Star Karate (Wii)[2010] */
/* Karaoke Revolution (Wii)[2010] */
if ((read_u32be(0x04,sf_h) == 0x00000800 || if ((read_u32be(0x04,sf_h) == 0x00000800 ||
read_u32be(0x04,sf_h) == 0x00000700) && /* rare? */ read_u32be(0x04,sf_h) == 0x00000700) && /* rare? */
read_u32be(0x08,sf_h) != 0x00000000 && 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->codec = DSP;
strwav->coefs_table = 0x7c; strwav->coefs_table = 0x7c;
strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000; 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; return 1;
} }
/* The House of the Dead: Overkill (PS3)[2009] (not Blitz but still the same format) */ /* 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 || if ((read_u32be(0x04,sf_h) == 0x00000800 ||
read_u32be(0x04,sf_h) == 0x00000700) && /* rare? */ read_u32be(0x04,sf_h) == 0x00000700) && /* rare? */
read_u32be(0x08,sf_h) != 0x00000000 && 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->sample_rate = read_s32be(0x38,sf_h);
strwav->flags = read_u32be(0x3c,sf_h); strwav->flags = read_u32be(0x3c,sf_h);
strwav->channels = read_s32be(0x70,sf_h); /* tracks of 1ch */ strwav->channels = read_s32be(0x70,sf_h);
strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000;
/* 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"); ;VGM_LOG("STR+WAV: header HOTDO (PS3)\n");
return 1; 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 "../vgmstream.h"
#include "../layout/layout.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; goto fail;
} }
@ -13,9 +21,54 @@ bool layered_add_codec(VGMSTREAM* vs, int layers, int layer_channels) {
if (!layers) if (!layers)
layers = vs->channels / layer_channels; 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; int i;
layered_layout_data* data; 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) { switch(vs->layout_type) {
case layout_segmented: //to be improved case layout_segmented: //to be improved
goto fail; 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]->loop_end_sample = vs->loop_end_sample;
data->layers[i]->codec_data = vs->codec_data; 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]->coding_type = vs->coding_type;
data->layers[i]->layout_type = layout_none; 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 */ vs->codec_data = NULL; /* moved to layer, don't hold it */
@ -59,12 +115,29 @@ fail:
return false; 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) { bool layered_add_done(VGMSTREAM* vs) {
//TODO: some extra checks/setup? //TODO: some extra checks/setup?
if (!setup_layout_layered(vs->layout_data)) if (!setup_layout_layered(vs->layout_data))
goto fail; goto fail;
if (!vs->coding_type) {
layered_layout_data* data = vs->layout_data;
vs->coding_type = data->layers[0]->coding_type;
}
return true; return true;
fail: fail:
return false; return false;

View File

@ -3,6 +3,15 @@
#include "../vgmstream.h" #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) /* add a new layer from codec data (setups layout if needed)
* codec is passed in the vs for easier free/etc control */ * codec is passed in the vs for easier free/etc control */
bool layered_add_codec(VGMSTREAM* vs, int layers, int layer_channels); 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 { \ do { \
int i; \ int i; \
for (i=0; i < buf_size; 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"); \ if (bytes_per_line && (i+1) % bytes_per_line == 0) printf("\n"); \
} \ } \
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); 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 #ifdef VGM_USE_FFMPEG
/* check FFmpeg streams here, for lack of a better place */ /* 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) { 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 */ /* save start things so we can restart when seeking */
memcpy(vgmstream->start_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); memcpy(vgmstream->start_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels);
memcpy(vgmstream->start_vgmstream, vgmstream, sizeof(VGMSTREAM)); memcpy(vgmstream->start_vgmstream, vgmstream, sizeof(VGMSTREAM));