mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-02-21 04:48:21 +01:00
Merge pull request #1514 from bnnm/ea-etc
- Fix 6ch .sps opus [EA Sports FC24 (PC)] - Fix PCM-only EAMP3 [EA Sports FC24 (PC)] - Add .bgm extension to SVS [Unlimited Saga (PS2)] - Add HCA key
This commit is contained in:
commit
f4843db739
13
doc/USAGE.md
13
doc/USAGE.md
@ -123,16 +123,23 @@ Note the above is also affected by vgmstream's options *Enable common exts* (vgm
|
||||
will accept and play common files like `.wav` or `.ogg`), and *Enable unknown exts* (will
|
||||
try to play files outside the known extension list, which is often possible through *TXTH*).
|
||||
|
||||
#### Default title
|
||||
#### Default title and playlist columns
|
||||
By default *vgmstream* auto-generates a `title` tag depending on subsongs, stream name
|
||||
and other details. You can change this by setting *"override title"* in the options,
|
||||
that uses foobar's default (filename without extension) and tweating the display format
|
||||
in *Preferences > Display > Default User Interface* (may need to add some conditionals
|
||||
to handle files with/out subsongs). *vgmstream* automatically exports these tags:
|
||||
to handle files with/out subsongs).
|
||||
|
||||
*vgmstream* automatically exports these tags:
|
||||
- `STREAM_INDEX`: current subsong, if file has subsongs, starts from 1
|
||||
- `STREAM_COUNT`: total subsongs, if file has subsongs
|
||||
- `STREAM_NAME`: internal name, that also exists in some formats without subsongs
|
||||
For example: `[%artist% - ]%title% [%stream_index%][/ %stream_name%]`
|
||||
- `LOOP_START`: loop start, if any
|
||||
- `LOOP_END`: loop end, if any
|
||||
|
||||
Those exported tags can be used as columns as well (*.. > Playlist view > custom columns*).
|
||||
|
||||
Example: `[%artist% - ]%title% [%stream_index%][/ %stream_name%]`
|
||||
|
||||
You can also set an unique *Destination* pattern when converting to .wav (even without)
|
||||
setting *override title*). For example `[$num(%stream_index%,2)] %filename%[-%stream_name%]`
|
||||
|
@ -514,7 +514,7 @@ static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) {
|
||||
|
||||
/* set mapping family */
|
||||
if (cfg->channels > 2 || cfg->stream_count > 1) {
|
||||
mapping_family = 1; //todo test 255
|
||||
mapping_family = 1;
|
||||
header_size += 0x01 + 0x01 + cfg->channels; /* table size */
|
||||
}
|
||||
|
||||
@ -525,7 +525,7 @@ static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) {
|
||||
|
||||
if (header_size > buf_size) {
|
||||
VGM_LOG("OPUS: buffer can't hold header\n");
|
||||
goto fail;
|
||||
return 0;
|
||||
}
|
||||
|
||||
put_u32be(buf+0x00, get_id32be("Opus"));
|
||||
@ -539,21 +539,29 @@ static size_t make_opus_header(uint8_t* buf, int buf_size, opus_config *cfg) {
|
||||
|
||||
/* set mapping table */
|
||||
if (mapping_family > 0) {
|
||||
int i;
|
||||
/* test if external mappings are correctly set, as incorrect values result in wrong output
|
||||
* (ex. all 0s would mean "write channel L in every channel)")*/
|
||||
bool mappings_set = false;
|
||||
for (int i = 0; i < cfg->channels; i++) {
|
||||
if (cfg->channel_mapping[i]) {
|
||||
mappings_set = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* total streams (mono/stereo) */
|
||||
put_u8(buf+0x13, cfg->stream_count);
|
||||
/* stereo streams (6ch can be 2ch+2ch+1ch+1ch = 2 coupled in 4 streams) */
|
||||
put_u8(buf+0x14, cfg->coupled_count);
|
||||
|
||||
/* mapping per channel (order of channels, ex: 00 01 04 05 02 03) */
|
||||
for (i = 0; i < cfg->channels; i++) {
|
||||
put_u8(buf+0x15+i, cfg->channel_mapping[i]);
|
||||
for (int i = 0; i < cfg->channels; i++) {
|
||||
uint8_t mapping = (mappings_set) ? cfg->channel_mapping[i] : i;
|
||||
put_u8(buf+0x15+i, mapping);
|
||||
}
|
||||
}
|
||||
|
||||
return header_size;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t make_opus_comment(uint8_t* buf, int buf_size) {
|
||||
@ -568,11 +576,11 @@ static size_t make_opus_comment(uint8_t* buf, int buf_size) {
|
||||
|
||||
if (comment_size > buf_size) {
|
||||
VGM_LOG("OPUS: buffer can't hold comment\n");
|
||||
goto fail;
|
||||
return 0;
|
||||
}
|
||||
|
||||
put_u32be(buf+0x00, 0x4F707573); /* "Opus" header magic */
|
||||
put_u32be(buf+0x04, 0x54616773); /* "Tags" header magic */
|
||||
put_u32be(buf+0x00, get_id32be("Opus"));
|
||||
put_u32be(buf+0x04, get_id32be("Tags"));
|
||||
put_u32le(buf+0x08, vendor_string_length);
|
||||
memcpy (buf+0x0c, vendor_string, vendor_string_length);
|
||||
put_u32le(buf+0x0c + vendor_string_length+0x00, 1); /* user_comment_list_length */
|
||||
@ -580,8 +588,6 @@ static size_t make_opus_comment(uint8_t* buf, int buf_size) {
|
||||
memcpy (buf+0x0c + vendor_string_length+0x08, user_comment_0_string, user_comment_0_length);
|
||||
|
||||
return comment_size;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t make_oggs_first(uint8_t* buf, int buf_size, opus_config* cfg) {
|
||||
|
@ -15,42 +15,49 @@ typedef struct {
|
||||
uint32_t pcm_size; /* size of the PCM block */
|
||||
} eamp3_frame_info;
|
||||
|
||||
static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf);
|
||||
static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, eamp3_frame_info * eaf);
|
||||
static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start);
|
||||
static bool eamp3_parse_frame(STREAMFILE* sf, uint32_t offset, mpeg_codec_data* data, eamp3_frame_info* eaf);
|
||||
static int eamp3_write_pcm_block(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream, eamp3_frame_info* eaf);
|
||||
static int eamp3_skip_data(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, int num_stream, int at_start);
|
||||
|
||||
/* init config and validate */
|
||||
int mpeg_custom_setup_init_eamp3(STREAMFILE *streamFile, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) {
|
||||
mpeg_frame_info info;
|
||||
uint16_t frame_header;
|
||||
size_t header_size;
|
||||
int mpeg_custom_setup_init_eamp3(STREAMFILE* sf, off_t start_offset, mpeg_codec_data *data, coding_t *coding_type) {
|
||||
eamp3_frame_info eaf = {0};
|
||||
|
||||
|
||||
/* test unknown stuff */
|
||||
frame_header = (uint16_t)read_16bitLE(start_offset+0x00, streamFile);
|
||||
if (frame_header & 0x2000) {
|
||||
VGM_LOG("EAMP3: found unknown bit 13\n");
|
||||
goto fail;
|
||||
}
|
||||
if ((frame_header & 0x8000) && (uint32_t)read_32bitLE(start_offset+0x02, streamFile) > 0xFFFF) {
|
||||
VGM_LOG("EAMP3: found big PCM block\n");
|
||||
/* needed below for rare PCM-only cases (EAMP3 can use multilayers) */
|
||||
data->channels_per_frame = data->config.channels >= 2 ? 2 : 1;
|
||||
bool ok = eamp3_parse_frame(sf, start_offset, data, &eaf);
|
||||
if (!ok) goto fail;
|
||||
|
||||
/* checks */
|
||||
if (eaf.unknown_flag || (eaf.extended_flag && eaf.pcm_number > 0xFFFF) || (!eaf.pcm_number && !eaf.mpeg_size)) {
|
||||
VGM_LOG("EAMP3: found data found\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* get frame info at offset */
|
||||
header_size = (frame_header & 0x8000) ? 0x06 : 0x02;
|
||||
if (!mpeg_get_frame_info(streamFile, start_offset+header_size, &info))
|
||||
goto fail;
|
||||
switch(info.layer) {
|
||||
case 1: *coding_type = coding_MPEG_layer1; break;
|
||||
case 2: *coding_type = coding_MPEG_layer2; break;
|
||||
case 3: *coding_type = coding_MPEG_layer3; break;
|
||||
default: goto fail;
|
||||
if (!eaf.mpeg_size) {
|
||||
/* rare small pcm-only files, pretend mp3 max though won't be really needed [FC24 (PC)] */
|
||||
*coding_type = coding_MPEG_layer3;
|
||||
data->channels_per_frame = data->config.channels;
|
||||
data->samples_per_frame = 1152;
|
||||
data->bitrate_per_frame = 320;
|
||||
data->sample_rate_per_frame = 48000;
|
||||
}
|
||||
else {
|
||||
mpeg_frame_info info;
|
||||
if (!mpeg_get_frame_info(sf, start_offset + eaf.pre_size, &info))
|
||||
goto fail;
|
||||
switch(info.layer) {
|
||||
case 1: *coding_type = coding_MPEG_layer1; break;
|
||||
case 2: *coding_type = coding_MPEG_layer2; break;
|
||||
case 3: *coding_type = coding_MPEG_layer3; break;
|
||||
default: goto fail;
|
||||
}
|
||||
data->channels_per_frame = info.channels;
|
||||
data->samples_per_frame = info.frame_samples;
|
||||
data->bitrate_per_frame = info.bit_rate;
|
||||
data->sample_rate_per_frame = info.sample_rate;
|
||||
}
|
||||
data->channels_per_frame = info.channels;
|
||||
data->samples_per_frame = info.frame_samples;
|
||||
data->bitrate_per_frame = info.bit_rate;
|
||||
data->sample_rate_per_frame = info.sample_rate;
|
||||
|
||||
|
||||
return 1;
|
||||
@ -61,14 +68,14 @@ fail:
|
||||
/* reads custom frame header + MPEG data + (optional) PCM block */
|
||||
int mpeg_custom_parse_frame_eamp3(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream) {
|
||||
mpeg_custom_stream *ms = data->streams[num_stream];
|
||||
eamp3_frame_info eaf;
|
||||
eamp3_frame_info eaf = {0};
|
||||
int ok;
|
||||
|
||||
|
||||
if (!eamp3_skip_data(stream, data, num_stream, 1))
|
||||
goto fail;
|
||||
|
||||
ok = eamp3_parse_frame(stream, data, &eaf);
|
||||
ok = eamp3_parse_frame(stream->streamfile, stream->offset, data, &eaf);
|
||||
if (!ok) goto fail;
|
||||
|
||||
ms->bytes_in_buffer = read_streamfile(ms->buffer, stream->offset + eaf.pre_size, eaf.mpeg_size, stream->streamfile);
|
||||
@ -87,8 +94,8 @@ fail:
|
||||
}
|
||||
|
||||
|
||||
static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, eamp3_frame_info * eaf) {
|
||||
uint16_t current_header = (uint16_t)read_16bitLE(stream->offset+0x00, stream->streamfile);
|
||||
static bool eamp3_parse_frame(STREAMFILE* sf, uint32_t offset, mpeg_codec_data* data, eamp3_frame_info* eaf) {
|
||||
uint16_t current_header = read_u16le(offset+0x00, sf);
|
||||
|
||||
eaf->extended_flag = (current_header & 0x8000);
|
||||
eaf->stereo_flag = (current_header & 0x4000);
|
||||
@ -96,12 +103,12 @@ static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, ea
|
||||
eaf->frame_size = (current_header & 0x1FFF); /* full size including PCM block */
|
||||
eaf->pcm_number = 0;
|
||||
if (eaf->extended_flag > 0) {
|
||||
eaf->pcm_number = (uint32_t)read_32bitLE(stream->offset+0x02, stream->streamfile);
|
||||
eaf->pcm_size = sizeof(sample) * eaf->pcm_number * data->channels_per_frame;
|
||||
eaf->pcm_number = read_u32le(offset+0x02, sf);
|
||||
eaf->pcm_size = sizeof(int16_t) * eaf->pcm_number * data->channels_per_frame;
|
||||
eaf->pre_size = 0x06;
|
||||
eaf->mpeg_size = eaf->frame_size - eaf->pre_size - eaf->pcm_size;
|
||||
if (eaf->frame_size < eaf->pre_size + eaf->pcm_size) {
|
||||
VGM_LOG("EAMP3: bad pcm size at %x\n", (uint32_t)stream->offset);
|
||||
VGM_LOG("EAMP3: bad pcm size at %x: %x < %x + %x (%x * %x)\n", offset, eaf->frame_size, eaf->pre_size, eaf->pcm_size, eaf->pcm_number, data->channels_per_frame);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
@ -111,9 +118,9 @@ static int eamp3_parse_frame(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, ea
|
||||
eaf->mpeg_size = eaf->frame_size - eaf->pre_size;
|
||||
}
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
fail:
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* write PCM block directly to sample buffer and setup decode discard (see EALayer3). */
|
||||
@ -155,12 +162,12 @@ fail:
|
||||
/* Skip EA-frames from other streams for .sns/sps multichannel (see EALayer3). */
|
||||
static int eamp3_skip_data(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data, int num_stream, int at_start) {
|
||||
int ok, i;
|
||||
eamp3_frame_info eaf;
|
||||
eamp3_frame_info eaf = {0};
|
||||
int skips = at_start ? num_stream : data->streams_size - 1 - num_stream;
|
||||
|
||||
|
||||
for (i = 0; i < skips; i++) {
|
||||
ok = eamp3_parse_frame(stream, data, &eaf);
|
||||
ok = eamp3_parse_frame(stream->streamfile, stream->offset, data, &eaf);
|
||||
if (!ok) goto fail;
|
||||
|
||||
//;VGM_LOG("s%i: skipping %x, now at %lx\n", num_stream,eaf.frame_size,stream->offset);
|
||||
|
@ -251,8 +251,8 @@ fail:
|
||||
|
||||
static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE* sf_head, STREAMFILE* sf_data, eaac_header_t* eaac);
|
||||
static layered_layout_data* build_layered_eaaudiocore(STREAMFILE* sf, eaac_header_t *eaac, off_t start_offset);
|
||||
static STREAMFILE *setup_eaac_streamfile(eaac_header_t *ea, STREAMFILE* sf_head, STREAMFILE* sf_data);
|
||||
static size_t calculate_eaac_size(STREAMFILE* sf, eaac_header_t *ea, uint32_t num_samples, off_t start_offset, int is_ram);
|
||||
static STREAMFILE* setup_eaac_streamfile(eaac_header_t* ea, STREAMFILE* sf_head, STREAMFILE* sf_data);
|
||||
static size_t calculate_eaac_size(STREAMFILE* sf, eaac_header_t* ea, uint32_t num_samples, off_t start_offset, int is_ram);
|
||||
|
||||
|
||||
static VGMSTREAM* init_vgmstream_eaaudiocore_main(eaac_header_t* eaac, STREAMFILE* sf_head, STREAMFILE* sf_data, off_t header_offset, off_t _start_offset, meta_t meta_type, bool standalone) {
|
||||
@ -469,18 +469,30 @@ static VGMSTREAM* init_vgmstream_eaaudiocore_main(eaac_header_t* eaac, STREAMFIL
|
||||
}
|
||||
else {
|
||||
switch(eaac->channels) {
|
||||
//case 8: cfg.coupled_count = 3; break; /* 2ch+2ch+2ch+1ch+1ch, 5 streams */
|
||||
//case 6: cfg.coupled_count = 2; break; /* 2ch+2ch+1ch+1ch, 4 streams */
|
||||
case 4: cfg.coupled_count = 2; break; /* 2ch+2ch, 2 streams */
|
||||
case 2: cfg.coupled_count = 1; break; /* 2ch, 1 stream */
|
||||
case 1: cfg.coupled_count = 0; break; /* 1ch, 1 stream [Madden 22 (PC)] */
|
||||
default: goto fail; /* possibly: streams = Nch / 2, coupled = Nch % 2 */
|
||||
//case 8: cfg.coupled_count = 3; break; /* 2ch+2ch+2ch+1ch+1ch, 5 streams */
|
||||
case 6: cfg.coupled_count = 2; break; /* 2ch+2ch+1ch+1ch, 4 streams [FC24 (PC)] */
|
||||
case 4: cfg.coupled_count = 2; break; /* 2ch+2ch, 2 streams */
|
||||
case 2: cfg.coupled_count = 1; break; /* 2ch, 1 stream */
|
||||
case 1: cfg.coupled_count = 0; break; /* 1ch, 1 stream [Madden 22 (PC)] */
|
||||
default:
|
||||
VGM_LOG("EAAC: unknown coupled count for %i\n", eaac->channels);
|
||||
goto fail; /* possibly: streams = Nch / 2, coupled = Nch % 2 */
|
||||
}
|
||||
}
|
||||
|
||||
/* total number internal OPUS streams (should be >0) */
|
||||
cfg.stream_count = cfg.channels - cfg.coupled_count;
|
||||
|
||||
/* observed mapping, basically swaps BL<>LFE (4ch and below are fine with defaults)*/
|
||||
if (eaac->channels == 6) { /* FL FR FC LFE BL BR */
|
||||
cfg.channel_mapping[0] = 0;
|
||||
cfg.channel_mapping[1] = 1;
|
||||
cfg.channel_mapping[2] = 2;
|
||||
cfg.channel_mapping[3] = 5;
|
||||
cfg.channel_mapping[4] = 4;
|
||||
cfg.channel_mapping[5] = 3;
|
||||
}
|
||||
|
||||
/* We *don't* remove EA blocks b/c in Multi Opus 1 block = 1 Opus packet
|
||||
* Regular EAOPUS uses layers to fake multichannel, this is normal multichannel Opus.
|
||||
* This can be used for stereo too, so probably replaces EAOPUS. */
|
||||
|
@ -1308,6 +1308,9 @@ static const hcakey_info hcakey_list[] = {
|
||||
// Puyo Puyo Puzzle Pop (iOS)
|
||||
{9999}, // 000000000000270F
|
||||
|
||||
// Penny Blood: Hellbound (PC)
|
||||
{845873498572}, // 00000C4F1FD49CC
|
||||
|
||||
};
|
||||
|
||||
#endif/*_HCA_KEYS_H_*/
|
||||
|
@ -40,7 +40,7 @@ typedef struct {
|
||||
} ktsr_header;
|
||||
|
||||
static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa);
|
||||
static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf);
|
||||
static bool parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf);
|
||||
static layered_layout_data* build_layered_atrac9(ktsr_header* ktsr, STREAMFILE *sf, uint32_t config_data);
|
||||
static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, ktsr_header* ktsr, VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf), const char* ext);
|
||||
|
||||
@ -113,6 +113,11 @@ static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa) {
|
||||
if (!parse_ktsr(&ktsr, sf))
|
||||
goto fail;
|
||||
|
||||
if (ktsr.total_subsongs == 0) {
|
||||
vgm_logi("KTSR: file has no subsongs\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* open companion body */
|
||||
if (ktsr.is_external) {
|
||||
if (ktsr.is_srsa) {
|
||||
@ -371,7 +376,7 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, uint32_t offset) {
|
||||
static bool parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, uint32_t offset) {
|
||||
uint32_t suboffset, starts_offset, sizes_offset;
|
||||
int i;
|
||||
uint32_t type;
|
||||
@ -484,10 +489,10 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, uint32_t offset
|
||||
if (!parse_codec(ktsr))
|
||||
goto fail;
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
fail:
|
||||
VGM_LOG("ktsr: error parsing subheader\n");
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ktsr engine reads+decrypts in the same func based on passed flag tho (reversed from exe)
|
||||
@ -583,7 +588,7 @@ static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf) {
|
||||
}
|
||||
}
|
||||
|
||||
static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
|
||||
static bool parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
|
||||
uint32_t offset, end, header_offset, name_offset;
|
||||
uint32_t stream_count;
|
||||
|
||||
@ -672,6 +677,10 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
|
||||
offset += size;
|
||||
}
|
||||
|
||||
if (ktsr->total_subsongs == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ktsr->target_subsong > ktsr->total_subsongs)
|
||||
goto fail;
|
||||
|
||||
@ -687,8 +696,8 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
|
||||
ktsr->extra_offset += ktsr->base_offset; /* ? */
|
||||
}
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
fail:
|
||||
vgm_logi("KTSR: unknown variation (report)\n");
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
@ -1,54 +1,43 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../util/meta_utils.h"
|
||||
|
||||
|
||||
/* SVS - SeqVagStream from Square games [Unlimited Saga (PS2) music] */
|
||||
VGMSTREAM * init_vgmstream_svs(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int channel_count, loop_flag, pitch;
|
||||
|
||||
VGMSTREAM* init_vgmstream_svs(STREAMFILE* sf) {
|
||||
|
||||
/* checks */
|
||||
/* .svs: header id (probably ok like The Bouncer's .vs) */
|
||||
if (!check_extensions(streamFile, "svs"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x53565300) /* "SVS\0" */
|
||||
goto fail;
|
||||
if (!is_id32be(0x00,sf, "SVS\0"))
|
||||
return NULL;
|
||||
/* .bgm: from debug strings (music%3.3u.bgm)
|
||||
* .svs: header id (probably ok like The Bouncer's .vs, there are also refs to "vas") */
|
||||
if (!check_extensions(sf, "bgm,svs"))
|
||||
return NULL;
|
||||
|
||||
meta_header_t h = {
|
||||
.meta = meta_SVS,
|
||||
};
|
||||
/* 0x04: flags (1=stereo?, 2=loop) */
|
||||
pitch = read_32bitLE(0x10,streamFile); /* usually 0x1000 = 48000 */
|
||||
//h.loop_start = read_s32le(0x08,sf) * 28; /* frame count (0x10 * ch) */
|
||||
h.loop_end = read_s32le(0x0c,sf) * 28; /* frame count (not exact num_samples when no loop) */
|
||||
int pitch = read_s32le(0x10,sf); /* usually 0x1000 = 48000 */
|
||||
/* 0x14: volume? */
|
||||
/* 0x18: file id (may be null) */
|
||||
/* 0x1c: null */
|
||||
h.stream_offset = 0x20;
|
||||
h.stream_size = get_streamfile_size(sf) - h.stream_offset;
|
||||
|
||||
loop_flag = (read_32bitLE(0x08,streamFile) > 0); /* loop start frame, min is 1 */
|
||||
channel_count = 2;
|
||||
start_offset = 0x20;
|
||||
h.channels = 2;
|
||||
h.sample_rate = round10((48000 * pitch) / 4096); /* music = ~44100, ambience = 48000 (rounding makes more sense but not sure) */
|
||||
h.num_samples = ps_bytes_to_samples(h.stream_size, h.channels);
|
||||
/* loop start/end on the same frame rarely happens too (ex. file_id 63 SVS), perhaps loop should be +1 */
|
||||
h.loop_flag = (h.loop_start > 0); /* min is 1 */
|
||||
|
||||
h.coding = coding_PSX;
|
||||
h.layout = layout_interleave;
|
||||
h.interleave = 0x10;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_SVS;
|
||||
vgmstream->sample_rate = round10((48000 * pitch) / 4096); /* music = ~44100, ambience = 48000 (rounding makes more sense but not sure) */
|
||||
vgmstream->num_samples = ps_bytes_to_samples(get_streamfile_size(streamFile) - start_offset, channel_count);
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = read_32bitLE(0x08,streamFile) * 28; /* frame count (0x10*ch) */
|
||||
vgmstream->loop_end_sample = read_32bitLE(0x0c,streamFile) * 28; /* frame count, (not exact num_samples when no loop) */
|
||||
/* start/end on the same frame rarely happens too (ex. file_id 63 SVS), perhaps loop should be +1 */
|
||||
}
|
||||
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x10;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
h.sf = sf;
|
||||
h.open_stream = true;
|
||||
return alloc_metastream(&h);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user