mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-12 01:30:49 +01:00
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:
commit
17de674a7d
@ -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):
|
||||||
|
152
doc/FORMATS.md
152
doc/FORMATS.md
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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" />
|
||||||
|
@ -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>
|
||||||
|
@ -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]
|
||||||
};
|
};
|
||||||
|
168
src/meta/ktsr.c
168
src/meta/ktsr.c
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
21
src/meta/str_wav_streamfile.h
Normal file
21
src/meta/str_wav_streamfile.h
Normal 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
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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"); \
|
||||||
|
@ -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));
|
||||||
|
Loading…
Reference in New Issue
Block a user