diff --git a/doc/FORMATS.md b/doc/FORMATS.md index 4cec1e23..9dfc0a8d 100644 --- a/doc/FORMATS.md +++ b/doc/FORMATS.md @@ -161,9 +161,9 @@ different internally (encrypted, different versions, etc) and not always can be - *ads_container*: `.ads .cads` - Subfiles: *ads* - Codecs: PCM16LE DVI_IMA_int PSX -- **nps.c** +- **npsf.c** - Namco NPSF header [*NPS*] - - *nps*: `.nps .npsf` + - *npsf*: `.nps .npsf` - Codecs: PSX - **xa.c** - Sony XA header [*XA*] @@ -200,7 +200,7 @@ different internally (encrypted, different versions, etc) and not always can be - Electronic Arts BNK header [*EA_BNK*] - Electronic Arts SCHl header [*EA_SCHL*] - *ea_schl*: `.asf .lasf .str .chk .eam .exa .sng .aud .sx .xa .strm .stm .hab .xsf .gsf .(extensionless)` - - *ea_schl_video*: `.uv .dct .mad .wve .vp6` + - *ea_schl_video*: `.uv .dct .mad .wve .vp6 .mpc .lmpc` - *ea_bnk*: `.bnk .sdt .hdt .ldt .abk .ast .cat` - *ea_abk*: `.abk + .ast` - *ea_hdr_dat*: `.hdr + .dat` @@ -241,7 +241,7 @@ different internally (encrypted, different versions, etc) and not always can be - **aifc.c** - Apple AIFF-C header [*AIFC*] - Apple AIFF header [*AIFF*] - - *aifc*: `.aif .laif .wav .lwav .(extensionless) .aifc .laifc .afc .cbd2 .bgm .fda .n64 .xa .caf .aiff .laiff .acm .adp .ai .pcm` + - *aifc*: `.aif .laif .wav .lwav .aiff .laiff .(extensionless) .aifc .laifc .afc .cbd2 .bgm .fda .n64 .xa .caf .acm .adp .ai .pcm` - Codecs: SDX2 CBD2 DVI_IMA_int APPLE_IMA4 RELIC VADPCM PCM8 PCM16BE XA - **str_snds.c** - 3DO SNDS header [*STR_SNDS*] @@ -271,7 +271,7 @@ different internally (encrypted, different versions, etc) and not always can be - RIFF WAVE header (ctrl looping) [*RIFF_WAVE_MWV*] - RIFX WAVE header [*RIFX_WAVE*] - RIFX WAVE header (smpl looping) [*RIFX_WAVE_smpl*] - - *riff*: `.wav .lwav .xwav .mwv .da .dax .cd .med .snd .adx .adp .xss .xsew .adpcm .adw .wd .(extensionless) .sbv .wvx .str .at3 .rws .aud .at9 .ckd .saf .ima .nsa .pcm .xvag .ogg .logg .p1d .xms .mus .dat .ldat .wma .lwma .caf` + - *riff*: `.wav .lwav .xwav .mwv .da .dax .cd .med .snd .adx .adp .xss .xsew .adpcm .adw .wd .(extensionless) .sbv .wvx .str .at3 .rws .aud .at9 .ckd .saf .ima .nsa .pcm .xvag .ogg .logg .p1d .xms .mus .dat .ldat .wma .lwma .caf .wax` - *rifx*: `.wav .lwav` - Codecs: AICA_int PCM32LE PCM24LE PCM16BE PCM16LE PCM8_U MSADPCM IMA PCMFLOAT MS_IMA AICA MPEG_custom XBOX_IMA MS_IMA_3BIT DVI_IMA L5_555 OGG_VORBIS ATRAC9 ATRAC3 MPEG MSADPCM_int - **nwa.c** @@ -402,12 +402,6 @@ different internally (encrypted, different versions, etc) and not always can be - *ps2_rkv*: `.rkv` - *ngc_rkv*: `.(extensionless) .rkv .bo2` - Codecs: PSX NGC_DSP -- **ps2_vas.c** - - Konami .VAS header [*PS2_VAS*] - - *ps2_vas*: `.vas` - - *ps2_vas_container*: `.vas` - - Subfiles: *ps2_vas* - - Codecs: PSX - **lp_ap_lep.c** - Konami LP/AP/LEP header [*LP_AP_LEP*] - *lp_ap_lep*: `.bin .lbin .lp .lep .ap` @@ -559,8 +553,8 @@ different internally (encrypted, different versions, etc) and not always can be - *ydsp*: `.ydsp` - Codecs: NGC_DSP - **ngc_ssm.c** - - SSM DSP Header [*NGC_SSM*] - - *ngc_ssm*: `.ssm` + - HAL Laboratory .SSM Header [*SSM*] + - *ssm*: `.ssm` - Codecs: NGC_DSP - **ps2_joe.c** - Asobo Studio .JOE header [*PS2_JOE*] @@ -1274,7 +1268,7 @@ different internally (encrypted, different versions, etc) and not always can be - **h4m.c** - Hudson HVQM4 header [*H4M*] - *h4m*: `.h4m .hvqm` - - Codecs: H4M_IMA + - Codecs: H4M_IMA NGC_AFC - **asf.c** - Argonaut ASF header [*ASF*] - *asf*: `.asf .lasf` @@ -1674,7 +1668,7 @@ different internally (encrypted, different versions, etc) and not always can be - **acx.c** - (container) - *acx*: `.acx` - - Subfiles: *adx* + - Subfiles: *ogg_vorbis adx* - **compresswave.c** - CompressWave .cwav header [*COMPRESSWAVE*] - *compresswave*: `.cwav` @@ -1816,14 +1810,24 @@ different internally (encrypted, different versions, etc) and not always can be - Traveller's Tales CBX header [*CBX*] - *cbx*: `.cbx` - Codecs: EA_MT -- **scd_pcm.c** - - Lunar: Eternal Blue .PCM header [*SCD_PCM*] - - *scd_pcm*: `.pcm` - - Codecs: PCM8_SB +- **vas_rockstar.c** + - Rockstar .VAS header [*VAS_ROCKSTAR*] + - *vas_rockstar*: `.vas` + - Codecs: PSX - **agsc.c** - Retro Studios AGSC header [*AGSC*] - *agsc*: `.agsc` - Codecs: NGC_DSP +- **scd_pcm.c** + - Lunar: Eternal Blue .PCM header [*SCD_PCM*] + - *scd_pcm*: `.pcm` + - Codecs: PCM8_SB +- **vas_kceo.c** + - Konami .VAS header [*VAS_KCEO*] + - *vas_kceo*: `.vas` + - *vas_kceo_container*: `.vas` + - Subfiles: *vas_kceo* + - Codecs: PSX - **ps2_wmus.c** - assumed The Warriors Sony ADPCM by .wmus extension [*PS2_WMUS*] - *ps2_wmus*: `.wmus` diff --git a/doc/TXTH.md b/doc/TXTH.md index 22b41ff5..39da6769 100644 --- a/doc/TXTH.md +++ b/doc/TXTH.md @@ -111,6 +111,8 @@ as explained below, but often will use default values. Accepted codec strings: # - ALAW A-Law 8-bit PCM # * For few rare games [Illwinter Game Design games: Conquest of Elysium 3 (PC), Dominions 3/4 (PC)] # * Interleave is multiple of 0x1 (default) +# - DPCM_KCEJ DPCM 8-bit (KCE Japan) +# * For rare games [Metal Gear Solid 2 (PS2)-cutscenes] # # - IMA IMA ADPCM (mono/stereo) # * For some PC games, and rarely consoles diff --git a/src/base/decode.c b/src/base/decode.c index 6e1b13c0..83834c3e 100644 --- a/src/base/decode.c +++ b/src/base/decode.c @@ -387,6 +387,7 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) { case coding_ACM: case coding_DERF: case coding_WADY: + case coding_DPCM_KCEJ: case coding_NWA: case coding_SASSC: case coding_CIRCUS_ADPCM: @@ -608,6 +609,7 @@ int decode_get_frame_size(VGMSTREAM* vgmstream) { case coding_CBD2_int: case coding_DERF: case coding_WADY: + case coding_DPCM_KCEJ: case coding_NWA: case coding_SASSC: case coding_CIRCUS_ADPCM: @@ -1137,6 +1139,12 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_ vgmstream->channels, vgmstream->samples_into_block, samples_to_do); } break; + case coding_DPCM_KCEJ: + for (ch = 0; ch < vgmstream->channels; ch++) { + decode_dpcm_kcej(&vgmstream->ch[ch], buffer+ch, + vgmstream->channels, vgmstream->samples_into_block, samples_to_do); + } + break; case coding_CIRCUS_ADPCM: for (ch = 0; ch < vgmstream->channels; ch++) { decode_circus_adpcm(&vgmstream->ch[ch], buffer+ch, diff --git a/src/coding/coding.h b/src/coding/coding.h index 2cc218ce..f26193c9 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -248,6 +248,9 @@ void decode_derf(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, /* wady_decoder */ void decode_wady(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +/* dpcm_kcej_decoder */ +void decode_dpcm_kcej(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); + /* circus_decoder */ typedef struct circus_codec_data circus_codec_data; diff --git a/src/coding/dpcm_kcej_decoder.c b/src/coding/dpcm_kcej_decoder.c new file mode 100644 index 00000000..601ecdcb --- /dev/null +++ b/src/coding/dpcm_kcej_decoder.c @@ -0,0 +1,44 @@ +#include "coding.h" + + +static int expand_code(uint8_t code) { + int neg = code & 0x80; + int cmd = code & 0x07; + + int v; + if (cmd == 7) + v = (code & 0x78) << 8; + else + v = ((code & 0x78) | 0x80) << 7; + v = (v >> cmd); + if (neg) + v = -v; + return v; +} + +/* from the decompilation, mono mode seems to do this: + * hist_a += expand_code(codes[i++]) + * hist_b += decode_byte(codes[i++]) + * sample = (hist_a + hist_b) / 2; + * out[s++] = sample; //L + * out[s++] = sample; //R, repeated for to make fake stereo) + * Existing files seem to be all stereo though +*/ + +/* decompiled from the exe */ +void decode_dpcm_kcej(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { + off_t frame_offset = stream->offset; /* frame size is 1 */ + int32_t hist = stream->adpcm_history1_32; + + int sample_pos = 0; + for (int i = first_sample; i < first_sample + samples_to_do; i++) { + uint8_t code = read_u8(frame_offset + i, stream->streamfile); + + hist += expand_code(code); + + outbuf[sample_pos] = hist; /* no clamp */ + sample_pos += channelspacing; + } + + stream->adpcm_history1_32 = hist; +} diff --git a/src/formats.c b/src/formats.c index 3061f97c..edd5cea7 100644 --- a/src/formats.c +++ b/src/formats.c @@ -880,6 +880,7 @@ static const coding_info coding_info_list[] = { {coding_SASSC, "Activision / EXAKT SASSC 8-bit DPCM"}, {coding_DERF, "Xilam DERF 8-bit DPCM"}, {coding_WADY, "Marble WADY 8-bit DPCM"}, + {coding_DPCM_KCEJ, "Konami 8-bit DPCM"}, {coding_NWA, "VisualArt's NWA DPCM"}, {coding_ACM, "InterPlay ACM"}, {coding_CIRCUS_ADPCM, "Circus 8-bit ADPCM"}, @@ -1087,7 +1088,7 @@ static const meta_info meta_info_list[] = { {meta_SCD_PCM, "Lunar: Eternal Blue .PCM header"}, {meta_PS2_PCM, "Konami .PCM header"}, {meta_PS2_RKV, "Legacy of Kain - Blood Omen 2 RKV PS2 header"}, - {meta_PS2_VAS, "Konami .VAS header"}, + {meta_VAS_KCEO, "Konami .VAS header"}, {meta_LP_AP_LEP, "Konami LP/AP/LEP header"}, {meta_SDT, "High Voltage .sdt header"}, {meta_WVS, "Swingin' Ape .WVS header"}, @@ -1125,7 +1126,7 @@ static const meta_info meta_info_list[] = { {meta_ISH_ISD, "ISH+ISD DSP Header"}, {meta_GSND, "Tecmo GSND Header"}, {meta_YDSP, "Yuke's YDSP Header"}, - {meta_NGC_SSM, "SSM DSP Header"}, + {meta_SSM, "HAL Laboratory .SSM Header"}, {meta_PS2_JOE, "Asobo Studio .JOE header"}, {meta_VGS, "Guitar Hero VGS Header"}, {meta_DCS_WAV, "In Utero DCS+WAV header"}, @@ -1431,7 +1432,7 @@ static const meta_info meta_info_list[] = { {meta_NXOF, "Nihon Falcom FDK header"}, {meta_GWB_GWD, "Ubisoft GWB+GWD header"}, {meta_CBX, "Traveller's Tales CBX header"}, - {meta_VAS, "Manhunt 2 .VAS header"}, + {meta_VAS_ROCKSTAR, "Rockstar .VAS header"}, }; void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) { diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index 5214404d..e237ed2b 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -185,6 +185,7 @@ + @@ -222,6 +223,7 @@ + @@ -545,7 +547,7 @@ - + @@ -588,7 +590,6 @@ - @@ -693,7 +694,8 @@ - + + @@ -765,6 +767,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index 6ab9eb91..a76ab3a4 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -380,6 +380,9 @@ util\Header Files + + util\Header Files + util\Header Files @@ -487,6 +490,9 @@ coding\Source Files + + coding\Source Files + coding\Source Files @@ -1456,7 +1462,7 @@ meta\Source Files - + meta\Source Files @@ -1585,9 +1591,6 @@ meta\Source Files - - meta\Source Files - meta\Source Files @@ -1900,7 +1903,10 @@ meta\Source Files - + + meta\Source Files + + meta\Source Files @@ -2116,6 +2122,9 @@ util\Source Files + + util\Source Files + util\Source Files diff --git a/src/meta/fsb_encrypted.c b/src/meta/fsb_encrypted.c index 9bcd1940..64f4e50c 100644 --- a/src/meta/fsb_encrypted.c +++ b/src/meta/fsb_encrypted.c @@ -65,8 +65,7 @@ static VGMSTREAM* test_fsbkey(STREAMFILE* sf, const uint8_t* key, size_t key_siz int test_std = flags & FLAG_STD; int test_alt = flags & FLAG_ALT; - - if (!vc && test_std) { + if (!vc && test_std && test_fsb_streamfile(sf, key, key_size, 0)) { temp_sf = setup_fsb_streamfile(sf, key, key_size, 0); if (!temp_sf) return NULL; //;dump_streamfile(temp_sf, 0); @@ -77,7 +76,7 @@ static VGMSTREAM* test_fsbkey(STREAMFILE* sf, const uint8_t* key, size_t key_siz close_streamfile(temp_sf); } - if (!vc && test_alt) { + if (!vc && test_alt && test_fsb_streamfile(sf, key, key_size, 1)) { temp_sf = setup_fsb_streamfile(sf, key, key_size, 1); if (!temp_sf) return NULL; //;dump_streamfile(temp_sf, 1); diff --git a/src/meta/fsb_encrypted_streamfile.h b/src/meta/fsb_encrypted_streamfile.h index fdde3f2f..0cbf6dd4 100644 --- a/src/meta/fsb_encrypted_streamfile.h +++ b/src/meta/fsb_encrypted_streamfile.h @@ -71,4 +71,23 @@ static STREAMFILE* setup_fsb_streamfile(STREAMFILE* sf, const uint8_t* key, size return new_sf; } +/* same as above but doesn't open streamfile, so it's a tiny bit faster */ +static bool test_fsb_streamfile(STREAMFILE* sf, const uint8_t* key, size_t key_size, int is_alt) { + fsb_decryption_data io_data = {0}; + + if (!key_size || key_size >= FSB_KEY_MAX) + return false; + + memcpy(io_data.key, key, key_size); + io_data.key_size = key_size; + io_data.is_alt = is_alt; + + /* setup subfile */ + uint8_t tmp[0x04]; + int bytes = fsb_decryption_read(sf, tmp, 0x00, 0x04, &io_data); + if (bytes != 0x04) + return false; + return (get_u32be(tmp) & 0xFFFFFF00) == get_id32be("FSB\0"); +} + #endif /* _FSB_ENCRYPTED_STREAMFILE_H_ */ diff --git a/src/meta/fsb_keys.h b/src/meta/fsb_keys.h index 9dd2f972..b110f43b 100644 --- a/src/meta/fsb_keys.h +++ b/src/meta/fsb_keys.h @@ -76,6 +76,7 @@ static const fsbkey_info fsbkey_list[] = { { MODE_FSB4_ALT, FSBKEY_ADD("tkdnsem000") }, // Ys Online: The Call of Solum (PC) [FSB3] (alt key: 2ED62676CEA6B60C0C0C) { MODE_FSB4_STD, FSBKEY_ADD("4DxgpNV3pQLPD6GT7g9Gf6eWU7SXutGQ") }, // Test Drive: Ferrari Racing Legends (PC) { MODE_FSB4_STD, FSBKEY_ADD("AjaxIsTheGoodestBoy") }, // Hello Kitty: Island Adventure (iOS) + { MODE_FSB5_STD, FSBKEY_ADD("resoforce") }, // Rivals of Aether 2 (PC) /* these games use a key per file, generated from the filename; could be possible to add them but there is a lot of songs, so external .fsbkey may be better (use guessfsb 3.1 with --write-key-file or ) */ diff --git a/src/meta/meta.h b/src/meta/meta.h index c2bd7397..5fde3a65 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -77,7 +77,7 @@ VGMSTREAM * init_vgmstream_rfrm(STREAMFILE *streamFile); VGMSTREAM* init_vgmstream_ads(STREAMFILE* sf); VGMSTREAM* init_vgmstream_ads_container(STREAMFILE* sf); -VGMSTREAM * init_vgmstream_nps(STREAMFILE *streamFile); +VGMSTREAM* init_vgmstream_npsf(STREAMFILE* sf); VGMSTREAM * init_vgmstream_rs03(STREAMFILE *streamFile); @@ -248,8 +248,8 @@ VGMSTREAM * init_vgmstream_ps2_pcm(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ps2_rkv(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_ps2_vas(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_ps2_vas_container(STREAMFILE * streamFile); +VGMSTREAM* init_vgmstream_vas_kceo(STREAMFILE* sf); +VGMSTREAM* init_vgmstream_vas_kceo_container(STREAMFILE* sf); VGMSTREAM* init_vgmstream_lp_ap_lep(STREAMFILE* sf); @@ -330,7 +330,7 @@ VGMSTREAM * init_vgmstream_ydsp(STREAMFILE * streamFile); VGMSTREAM* init_vgmstream_gsnd(STREAMFILE* sf); -VGMSTREAM * init_vgmstream_ngc_ssm(STREAMFILE * streamFile); +VGMSTREAM* init_vgmstream_ssm(STREAMFILE* sf); VGMSTREAM * init_vgmstream_ps2_joe(STREAMFILE * streamFile); @@ -1002,6 +1002,6 @@ VGMSTREAM* init_vgmstream_gwb_gwd(STREAMFILE* sf); VGMSTREAM* init_vgmstream_cbx(STREAMFILE* sf); -VGMSTREAM* init_vgmstream_vas(STREAMFILE* sf); +VGMSTREAM* init_vgmstream_vas_rockstar(STREAMFILE* sf); #endif /*_META_H*/ diff --git a/src/meta/ngc_ssm.c b/src/meta/ngc_ssm.c index fa82d362..a9808c92 100644 --- a/src/meta/ngc_ssm.c +++ b/src/meta/ngc_ssm.c @@ -1,96 +1,85 @@ #include "meta.h" -#include "../util.h" - -/* SSM (Golden Gashbell Full Power GC) */ -VGMSTREAM * init_vgmstream_ngc_ssm(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - char filename[PATH_LIMIT]; - off_t start_offset; - int loop_flag; - int channel_count; - int coef1_start; - int coef2_start; - int second_channel_start; - - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("ssm",filename_extension(filename))) - goto fail; - - /* check header */ -#if 0 - if (read_32bitBE(0x00,streamFile) != 0x0) - goto fail; -#endif - - loop_flag = (uint32_t)read_16bitBE(0x18,streamFile); - channel_count = read_32bitBE(0x10,streamFile); - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - /* fill in the vital statistics */ - start_offset = read_32bitBE(0x0,streamFile); - vgmstream->channels = channel_count; - vgmstream->sample_rate = read_32bitBE(0x14,streamFile); - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->num_samples = read_32bitBE(0x04,streamFile)*14/8/channel_count; - if (loop_flag) { - vgmstream->loop_start_sample = read_32bitBE(0x24,streamFile)*14/8/channel_count; - vgmstream->loop_end_sample = read_32bitBE(0x20,streamFile)*14/8/channel_count; - } - vgmstream->layout_type = layout_none; - vgmstream->meta_type = meta_NGC_SSM; - - /* Retrieveing the coef tables and the start of the second channel*/ - coef1_start = 0x28; - coef2_start = 0x68; - second_channel_start = (read_32bitBE(0x04,streamFile)/2)+start_offset; - - { - int i; - for (i=0;i<16;i++) - vgmstream->ch[0].adpcm_coef[i] = read_16bitBE(coef1_start+i*2,streamFile); - if (channel_count == 2) { - for (i=0;i<16;i++) - vgmstream->ch[1].adpcm_coef[i] = read_16bitBE(coef2_start+i*2,streamFile); - } - } +#include "../util/meta_utils.h" +#include "../coding/coding.h" - /* open the file for reading */ - { - int i; - STREAMFILE * file; - file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!file) goto fail; - for (i=0;ich[i].streamfile = file; +/* .SSM - from Hal Laboratory games [Smash Bros Melee! (GC), Konjiki no Gashbell: YnTB Full Power (GC), Kururin Squash! (GC)] */ +VGMSTREAM* init_vgmstream_ssm(STREAMFILE* sf) { + meta_header_t h = {0}; - /* The first channel */ - vgmstream->ch[0].channel_start_offset= - vgmstream->ch[0].offset=start_offset; - /* The second channel */ - if (channel_count == 2) { - vgmstream->ch[1].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); + /* checks */ + h.head_size = read_u32be(0x00,sf); + h.data_size = read_u32be(0x04,sf); + h.total_subsongs = read_s32be(0x08,sf); + int file_id = read_u32be(0x0c,sf); - if (!vgmstream->ch[1].streamfile) goto fail; + /* extra tests + arbitrary maxes since no good header ID and values aren't exact */ + if (0x10 + h.head_size + h.data_size > get_streamfile_size(sf)) + return NULL; + if (h.head_size < h.total_subsongs * 0x48 || h.total_subsongs <= 0 || h.total_subsongs > 0x1000 || file_id > 0x1000) + return NULL; + if (!check_extensions(sf, "ssm")) + return NULL; - vgmstream->ch[1].channel_start_offset= - vgmstream->ch[1].offset=second_channel_start; - } - } - + h.target_subsong = sf->stream_index; + if (!check_subsongs(&h.target_subsong, h.total_subsongs)) + return NULL; + + /* sometimes there is padding after head_size, DSP's start ps matches this */ + h.data_offset = get_streamfile_size(sf) - h.data_size; //0x10 + h.head_size; + + + uint32_t offset = 0x10; + for (int i = 0; i < h.total_subsongs; i++) { + int channels = read_u32be(offset + 0x00,sf); + if (channels < 1 || channels > 2) return NULL; + + if (i + 1 == h.target_subsong) { + h.channels = read_u32be(offset + 0x00,sf); + h.sample_rate = read_s32be(offset + 0x04,sf); + + /* use first channel as base */ + h.loop_flag = read_s16be(offset + 0x08,sf); + h.loop_start = read_u32be(offset + 0x0c,sf); + h.chan_size = read_u32be(offset + 0x10,sf); + h.stream_offset = read_s32be(offset + 0x14,sf); + h.coefs_offset = offset + 0x18; + h.coefs_spacing = 0x40; + h.hists_offset = h.coefs_offset + 0x24; + h.hists_spacing = h.coefs_spacing; + if (h.channels >= 2) { + h.interleave = read_s32be(offset + 0x54,sf); /* use 2nd channel offset as interleave */ + } + + break; + } + + offset += 0x08 + channels * 0x40; } - return vgmstream; + /* oddly enough values are in absolute nibbles within the stream, adjust them here + * rarely may even point to a nibble after the header one (ex. 0x1005), but it's adjusted to 0x00 here */ + h.loop_start -= h.stream_offset; + h.chan_size -= h.stream_offset; + if (h.interleave) + h.interleave -= h.stream_offset; - /* clean up anything we may have opened */ -fail: - if (vgmstream) close_vgmstream(vgmstream); - return NULL; + h.loop_start = dsp_nibbles_to_samples(h.loop_start); + h.loop_end = dsp_nibbles_to_samples(h.chan_size); + h.num_samples = h.loop_end; + + h.stream_offset = (h.stream_offset / 0x10 * 0x08) + h.data_offset; + h.stream_size = (h.chan_size / 0x10 * 0x08 + (h.chan_size % 0x08 ? 0x08 : 0x00)) * h.channels; + h.interleave = h.interleave / 0x10 * 0x08; + + h.coding = coding_NGC_DSP; + h.layout = layout_interleave; //TODO layout flat + channel offset may be more appropriate + h.meta = meta_SSM; + + h.sf = sf; + h.big_endian = true; + h.open_stream = true; + + return alloc_metastream(&h); } - - - diff --git a/src/meta/nps.c b/src/meta/npsf.c similarity index 81% rename from src/meta/nps.c rename to src/meta/npsf.c index 7f2a1ef5..c013e81b 100644 --- a/src/meta/nps.c +++ b/src/meta/npsf.c @@ -2,25 +2,25 @@ #include "../coding/coding.h" /* NPFS - found in Namco NuSound v1 games [Tekken 5 (PS2), Venus & Braves (PS2), Ridge Racer (PSP)] */ -VGMSTREAM* init_vgmstream_nps(STREAMFILE* sf) { - VGMSTREAM * vgmstream = NULL; +VGMSTREAM* init_vgmstream_npsf(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; off_t start_offset; uint32_t channel_size; - int loop_flag, channel_count, loop_start, sample_rate; + int loop_flag, channels, loop_start, sample_rate; /* checks */ + if (!is_id32be(0x00, sf, "NPSF")) + return NULL; /* .nps: referenced extension (ex. Venus & Braves, Ridge Racer data files) * .npsf: header id (Namco Production Sound File?) */ - if ( !check_extensions(sf,"nps,npsf")) - goto fail; + if (!check_extensions(sf,"nps,npsf")) + return NULL; - if (read_u32be(0x00, sf) != 0x4E505346) /* "NPSF" */ - goto fail; /* 0x04: version? (0x00001000 = 1.00?) */ channel_size = read_s32le(0x08, sf); - channel_count = read_s32le(0x0C, sf); + channels = read_s32le(0x0C, sf); start_offset = read_s32le(0x10, sf); /* interleave? */ loop_start = read_s32le(0x14, sf); sample_rate = read_s32le(0x18, sf); @@ -36,7 +36,7 @@ VGMSTREAM* init_vgmstream_nps(STREAMFILE* sf) { /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); + vgmstream = allocate_vgmstream(channels, loop_flag); if (!vgmstream) goto fail; vgmstream->sample_rate = sample_rate; diff --git a/src/meta/txth.c b/src/meta/txth.c index 7ef7d939..72900508 100644 --- a/src/meta/txth.c +++ b/src/meta/txth.c @@ -56,6 +56,7 @@ typedef enum { YMZ, ULAW, ALAW, + DPCM_KCEJ, UNKNOWN = 255, } txth_codec_t; @@ -234,16 +235,16 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) { case PSX_bf: case HEVAG: interleave = 0x10; break; case NGC_DSP: interleave = 0x08; break; + case PCM_FLOAT_LE: interleave = 0x04; break; case PCM24LE: interleave = 0x03; break; case PCM24BE: interleave = 0x03; break; case PCM16LE: case PCM16BE: interleave = 0x02; break; case PCM8: case PCM8_U: - case PCM8_SB: interleave = 0x01; break; - case PCM_FLOAT_LE: interleave = 0x04; break; + case PCM8_SB: case ULAW: - case ALAW: interleave = 0x01; break; + case ALAW: default: break; } @@ -267,6 +268,7 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) { case PCM8_SB: coding = coding_PCM8_SB; break; case ULAW: coding = coding_ULAW; break; case ALAW: coding = coding_ALAW; break; + case DPCM_KCEJ: coding = coding_DPCM_KCEJ; break; case PCM_FLOAT_LE: coding = coding_PCMFLOAT; break; case SDX2: coding = coding_SDX2; break; case DVI_IMA: coding = coding_DVI_IMA; break; @@ -417,6 +419,12 @@ VGMSTREAM* init_vgmstream_txth(STREAMFILE* sf) { vgmstream->allow_dual_stereo = 1; /* known to be used in: PSX, AICA, YMZ */ break; + case coding_DPCM_KCEJ: + if (vgmstream->channels == 1) goto fail; /* untested/unknown */ + vgmstream->interleave_block_size = 0x01; + vgmstream->layout_type = layout_interleave; + break; + case coding_PCFX: vgmstream->interleave_block_size = txth.interleave; vgmstream->interleave_last_block_size = txth.interleave_last; @@ -1011,6 +1019,7 @@ static txth_codec_t parse_codec(txth_header* txth, const char* val) { else if (is_string(val,"HEVAG")) return HEVAG; else if (is_string(val,"ULAW")) return ULAW; else if (is_string(val,"ALAW")) return ALAW; + else if (is_string(val,"DPCM_KCEJ")) return DPCM_KCEJ; /* special handling */ else if (is_string(val,"name_value")) return txth->name_values[0]; else if (is_string(val,"name_value1")) return txth->name_values[0]; @@ -2157,6 +2166,7 @@ static int get_bytes_to_samples(txth_header* txth, uint32_t bytes) { case PCM8_SB: case ULAW: case ALAW: + case DPCM_KCEJ: return pcm8_bytes_to_samples(bytes, txth->channels); case PCM_FLOAT_LE: return pcm_bytes_to_samples(bytes, txth->channels, 32); diff --git a/src/meta/ps2_vas.c b/src/meta/vas_kceo.c similarity index 89% rename from src/meta/ps2_vas.c rename to src/meta/vas_kceo.c index d01c0561..4dd8d05c 100644 --- a/src/meta/ps2_vas.c +++ b/src/meta/vas_kceo.c @@ -2,8 +2,8 @@ #include "../coding/coding.h" -/* .VAS - from Konami Jikkyou Powerful Pro Yakyuu games */ -VGMSTREAM* init_vgmstream_ps2_vas(STREAMFILE* sf) { +/* .VAS - from Konami Computer Enterntainment Osaka games [Jikkyou Powerful Pro Yakyuu 8 (PS2), Jikkyou World Soccer 2000 (PS2)] */ +VGMSTREAM* init_vgmstream_vas_kceo(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; off_t start_offset; int loop_flag, channels; @@ -11,9 +11,9 @@ VGMSTREAM* init_vgmstream_ps2_vas(STREAMFILE* sf) { /* checks */ if (!check_extensions(sf, "vas")) - goto fail; + return NULL; if (read_u32le(0x00,sf) + 0x800 != get_streamfile_size(sf)) - goto fail; + return NULL; loop_flag = (read_u32le(0x10,sf) != 0); channels = 2; @@ -25,10 +25,10 @@ VGMSTREAM* init_vgmstream_ps2_vas(STREAMFILE* sf) { /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channels,loop_flag); + vgmstream = allocate_vgmstream(channels, loop_flag); if (!vgmstream) goto fail; - vgmstream->meta_type = meta_PS2_VAS; + vgmstream->meta_type = meta_VAS_KCEO; vgmstream->sample_rate = read_s32le(0x04,sf); vgmstream->coding_type = coding_PSX; @@ -50,7 +50,7 @@ fail: /* .VAS in containers */ -VGMSTREAM* init_vgmstream_ps2_vas_container(STREAMFILE* sf) { +VGMSTREAM* init_vgmstream_vas_kceo_container(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; STREAMFILE* temp_sf = NULL; off_t subfile_offset = 0; @@ -60,7 +60,7 @@ VGMSTREAM* init_vgmstream_ps2_vas_container(STREAMFILE* sf) { /* checks */ if (!check_extensions(sf, "vas")) - goto fail; + return NULL; if (read_u32be(0x00, sf) == 0xAB8A5A00) { /* fixed value */ @@ -139,7 +139,7 @@ VGMSTREAM* init_vgmstream_ps2_vas_container(STREAMFILE* sf) { temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, NULL); if (!temp_sf) goto fail; - vgmstream = init_vgmstream_ps2_vas(temp_sf); + vgmstream = init_vgmstream_vas_kceo(temp_sf); if (!vgmstream) goto fail; vgmstream->num_streams = total_subsongs; diff --git a/src/meta/vas.c b/src/meta/vas_rockstar.c similarity index 90% rename from src/meta/vas.c rename to src/meta/vas_rockstar.c index a582842e..bcd8e38b 100644 --- a/src/meta/vas.c +++ b/src/meta/vas_rockstar.c @@ -2,7 +2,7 @@ #include "../coding/coding.h" /* VAS - Manhunt 2 [PSP] blocked audio format */ -VGMSTREAM* init_vgmstream_vas(STREAMFILE* sf) { +VGMSTREAM* init_vgmstream_vas_rockstar(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; off_t stream_offset; size_t data_size, stream_size, block_size = 0x40; @@ -14,10 +14,10 @@ VGMSTREAM* init_vgmstream_vas(STREAMFILE* sf) { /* VAGs: v1, used in prerelease builds * 2AGs: v2, used in the final release */ if (!is_id32be(0x00, sf, "VAGs") && !is_id32be(0x00, sf, "2AGs")) - goto fail; + return NULL; if (!check_extensions(sf, "vas")) - goto fail; + return NULL; /* parse header */ @@ -26,7 +26,7 @@ VGMSTREAM* init_vgmstream_vas(STREAMFILE* sf) { if (read_u8(0x0A, sf)) goto fail; /* always 0? */ num_streams = read_u8(0x0B, sf); - if (num_streams < 1 || num_streams > 32) goto fail; + if (target_subsong < 0 || num_streams < 1 || num_streams > 32) goto fail; if (!target_subsong) target_subsong = 1; channels = 1; /* might be read_u8(0x0A, sf) + 1? */ @@ -56,7 +56,7 @@ VGMSTREAM* init_vgmstream_vas(STREAMFILE* sf) { vgmstream = allocate_vgmstream(channels, loop_flag); if (!vgmstream) goto fail; - vgmstream->meta_type = meta_VAS; + vgmstream->meta_type = meta_VAS_ROCKSTAR; vgmstream->coding_type = coding_PSX; vgmstream->num_streams = num_streams; vgmstream->sample_rate = sample_rate; diff --git a/src/util/meta_utils.c b/src/util/meta_utils.c new file mode 100644 index 00000000..eeecc2d2 --- /dev/null +++ b/src/util/meta_utils.c @@ -0,0 +1,48 @@ +#include "../vgmstream.h" +#include "meta_utils.h" + + +/* Allocate memory and setup a VGMSTREAM */ +VGMSTREAM* alloc_metastream(meta_header_t* h) { + + if (h->sample_rate <= 0 || h->sample_rate > VGMSTREAM_MAX_SAMPLE_RATE) + return NULL; + if (h->num_samples <= 0 || h->num_samples > VGMSTREAM_MAX_NUM_SAMPLES) + return NULL; + + VGMSTREAM* vgmstream = allocate_vgmstream(h->channels, h->loop_flag); + if (!vgmstream) return NULL; + + //vgmstream->channels = h->channels; + vgmstream->sample_rate = h->sample_rate; + vgmstream->num_samples = h->num_samples; + vgmstream->loop_start_sample = h->loop_start; + vgmstream->loop_end_sample = h->loop_end; + + vgmstream->coding_type = h->coding; + vgmstream->layout_type = h->layout; + vgmstream->meta_type = h->meta; + + vgmstream->num_streams = h->total_subsongs; + vgmstream->stream_size = h->stream_size; + vgmstream->interleave_block_size = h->interleave; + + + if (h->coding == coding_NGC_DSP && (h->sf || h->sf_head)) { + if (h->coefs_offset || h->coefs_spacing) + dsp_read_coefs(vgmstream, h->sf ? h->sf : h->sf_head, h->coefs_offset, h->coefs_spacing, h->big_endian); + if (h->hists_offset || h->hists_spacing) + dsp_read_hist (vgmstream, h->sf ? h->sf : h->sf_head, h->hists_offset, h->hists_spacing, h->big_endian); + } + + if (h->open_stream) { + if (!vgmstream_open_stream(vgmstream, h->sf ? h->sf : h->sf_head, h->stream_offset)) + goto fail; + } + + return vgmstream; +fail: + close_vgmstream(vgmstream); + return NULL; +} + diff --git a/src/util/meta_utils.h b/src/util/meta_utils.h new file mode 100644 index 00000000..a8676f50 --- /dev/null +++ b/src/util/meta_utils.h @@ -0,0 +1,61 @@ +#ifndef _META_UTILS_H +#define _META_UTILS_H + +#include "../streamtypes.h" +#include "reader_get.h" +#include "reader_put.h" +#include "../coding/coding.h" + + +/* Helper struct for common numbers (no need to use all), to use with helper functions. + * Preferably declare after validating header ID as it's faster (by a minuscule amount). */ +typedef struct { + /* should be set */ + int channels; + int sample_rate; + int32_t num_samples; + + /* optional info */ + bool loop_flag; + int32_t loop_start; + int32_t loop_end; + + int target_subsong; + int total_subsongs; + + int32_t interleave; + + /* common helpers */ + uint32_t stream_offset; /* where current stream starts */ + uint32_t stream_size; /* current stream size */ + uint32_t data_offset; /* where data (first stream) starts */ + uint32_t data_size; /* data for all streams */ + uint32_t head_size; /* size of some header part */ + uint32_t chan_offset; + uint32_t chan_size; + + uint32_t coefs_offset; + uint32_t coefs_spacing; + uint32_t hists_offset; + uint32_t hists_spacing; + + /* optional but can be used for some actions */ + bool big_endian; + coding_t coding; + layout_t layout; + meta_t meta; + + /* only sf_head is used to read coefs and such */ + STREAMFILE* sf; + STREAMFILE* sf_head; + STREAMFILE* sf_body; + + bool open_stream; +} meta_header_t; + +VGMSTREAM* alloc_metastream(meta_header_t* h); + +/* checks max subsongs and setups target */ +//bool check_subsongs(int* target_subsong, int total_subsongs); + +#endif diff --git a/src/vgmstream.c b/src/vgmstream.c index e5d763f2..9c4310cf 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -39,7 +39,7 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_cstr, init_vgmstream_gcsw, init_vgmstream_ads, - init_vgmstream_nps, + init_vgmstream_npsf, init_vgmstream_xa, init_vgmstream_rxws, init_vgmstream_ngc_dsp_stm, @@ -98,8 +98,6 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_vsv, init_vgmstream_ps2_pcm, init_vgmstream_ps2_rkv, - init_vgmstream_ps2_vas, - init_vgmstream_ps2_vas_container, init_vgmstream_lp_ap_lep, init_vgmstream_sdt, init_vgmstream_aix, @@ -142,7 +140,7 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_ish_isd, init_vgmstream_gsnd, init_vgmstream_ydsp, - init_vgmstream_ngc_ssm, + init_vgmstream_ssm, init_vgmstream_ps2_joe, init_vgmstream_vgs, init_vgmstream_dcs_wav, @@ -524,11 +522,13 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_gwb_gwd, init_vgmstream_s_pack, init_vgmstream_cbx, - init_vgmstream_vas, + init_vgmstream_vas_rockstar, /* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */ - init_vgmstream_scd_pcm, init_vgmstream_agsc, + init_vgmstream_scd_pcm, + init_vgmstream_vas_kceo, + init_vgmstream_vas_kceo_container, init_vgmstream_ps2_wmus, init_vgmstream_mib_mih, init_vgmstream_mjb_mjh, diff --git a/src/vgmstream_types.h b/src/vgmstream_types.h index 050ca9fa..bdc09baf 100644 --- a/src/vgmstream_types.h +++ b/src/vgmstream_types.h @@ -131,6 +131,7 @@ typedef enum { coding_SASSC, /* Activision EXAKT SASSC 8-bit DPCM */ coding_DERF, /* DERF 8-bit DPCM */ coding_WADY, /* WADY 8-bit DPCM */ + coding_DPCM_KCEJ, /* Konami Computer Entertainment Japan 8-bit DPCM */ coding_NWA, /* VisualArt's NWA DPCM */ coding_ACM, /* InterPlay ACM */ coding_CIRCUS_ADPCM, /* Circus 8-bit ADPCM */ @@ -344,7 +345,7 @@ typedef enum { meta_SCD_PCM, /* Lunar - Eternal Blue */ meta_PS2_PCM, /* Konami KCEJ East: Ephemeral Fantasia, Yu-Gi-Oh! The Duelists of the Roses, 7 Blades */ meta_PS2_RKV, /* Legacy of Kain - Blood Omen 2 (PS2) */ - meta_PS2_VAS, /* Pro Baseball Spirits 5 */ + meta_VAS_KCEO, meta_LP_AP_LEP, meta_SDT, /* Baldur's Gate - Dark Alliance */ meta_STR_SEGA, @@ -364,7 +365,7 @@ typedef enum { meta_FFCC_STR, /* Final Fantasy: Crystal Chronicles */ meta_UBI_JADE, /* Beyond Good & Evil, Rayman Raving Rabbids */ meta_GCA, /* Metal Slug Anthology */ - meta_NGC_SSM, /* Golden Gashbell Full Power */ + meta_SSM, meta_PS2_JOE, /* Wall-E / Pixar games */ meta_YMF, meta_SADL, @@ -705,7 +706,7 @@ typedef enum { meta_NXOF, meta_GWB_GWD, meta_CBX, - meta_VAS, + meta_VAS_ROCKSTAR, } meta_t;