2019-08-11 19:38:40 +02:00
|
|
|
#include "meta.h"
|
|
|
|
#include "../layout/layout.h"
|
|
|
|
#include "../coding/coding.h"
|
|
|
|
|
|
|
|
|
|
|
|
/* PSF single - Pivotal games single segment (external in some PC/Xbox or inside bigfiles) [The Great Escape, Conflict series] */
|
|
|
|
VGMSTREAM * init_vgmstream_psf_single(STREAMFILE *streamFile) {
|
|
|
|
VGMSTREAM * vgmstream = NULL;
|
|
|
|
off_t start_offset;
|
|
|
|
int loop_flag, channel_count, sample_rate, rate_value, interleave;
|
|
|
|
uint32_t psf_config;
|
|
|
|
uint8_t flags;
|
|
|
|
size_t data_size;
|
|
|
|
coding_t codec;
|
|
|
|
|
|
|
|
|
|
|
|
/* checks */
|
|
|
|
/* .psf: actual extension
|
|
|
|
* .swd: bigfile extension */
|
|
|
|
if (!check_extensions(streamFile, "psf,swd"))
|
|
|
|
goto fail;
|
|
|
|
if ((read_32bitBE(0x00,streamFile) & 0xFFFFFF00) != 0x50534600) /* "PSF\00" */
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
flags = read_8bit(0x03,streamFile);
|
|
|
|
switch(flags) {
|
|
|
|
case 0xC0: /* [The Great Escape (PS2), Conflict: Desert Storm (PS2)] */
|
2019-08-15 15:12:13 +02:00
|
|
|
case 0x40: /* [The Great Escape (PS2)] */
|
2019-08-11 19:38:40 +02:00
|
|
|
case 0xA1: /* [Conflict: Desert Storm 2 (PS2)] */
|
|
|
|
case 0x21: /* [Conflict: Desert Storm 2 (PS2), Conflict: Global Storm (PS2)] */
|
|
|
|
//case 0x22: /* [Conflict: Vietman (PS2)] */ //todo weird size value, stereo, only one found
|
2019-08-15 15:12:13 +02:00
|
|
|
codec = coding_PSX;
|
|
|
|
interleave = 0x10;
|
|
|
|
|
2019-08-11 19:38:40 +02:00
|
|
|
channel_count = 2;
|
2019-08-15 15:12:13 +02:00
|
|
|
if (flags == 0x21 || flags == 0x40)
|
2019-08-11 19:38:40 +02:00
|
|
|
channel_count = 1;
|
|
|
|
start_offset = 0x08;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x80: /* [The Great Escape (PC/Xbox), Conflict: Desert Storm (Xbox/GC), Conflict: Desert Storm 2 (Xbox)] */
|
|
|
|
case 0x81: /* [Conflict: Desert Storm 2 (Xbox), Conflict: Vietnam (Xbox)] */
|
|
|
|
case 0x01: /* [Conflict: Global Storm (Xbox)] */
|
2019-08-15 15:12:13 +02:00
|
|
|
codec = coding_PSX_pivotal;
|
|
|
|
interleave = 0x10;
|
|
|
|
|
2019-08-11 19:38:40 +02:00
|
|
|
channel_count = 2;
|
|
|
|
if (flags == 0x01)
|
|
|
|
channel_count = 1;
|
|
|
|
start_offset = 0x08;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xD1: /* [Conflict: Desert Storm 2 (GC)] */
|
|
|
|
codec = coding_NGC_DSP;
|
2019-08-15 15:12:13 +02:00
|
|
|
interleave = 0x08;
|
|
|
|
|
|
|
|
channel_count = 2;
|
2019-08-11 19:38:40 +02:00
|
|
|
start_offset = 0x08 + 0x60 * channel_count;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
loop_flag = 0;
|
|
|
|
|
|
|
|
psf_config = read_32bitLE(0x04, streamFile);
|
|
|
|
|
|
|
|
/* pitch/cents? */
|
|
|
|
rate_value = (psf_config >> 20) & 0xFFF;
|
|
|
|
switch(rate_value) {
|
2019-08-15 15:12:13 +02:00
|
|
|
case 3763: sample_rate = 44100; break;
|
|
|
|
case 1365: sample_rate = 16000; break;
|
|
|
|
case 940: sample_rate = 11050; break;
|
|
|
|
case 460: sample_rate = 5000; break;
|
2019-08-11 19:38:40 +02:00
|
|
|
default:
|
|
|
|
VGM_LOG("PSF: unknown rate value %x\n", rate_value);
|
2019-08-15 15:12:13 +02:00
|
|
|
sample_rate = rate_value * 11.72; /* not exact but works well enough */
|
|
|
|
break;
|
2019-08-11 19:38:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
data_size = (psf_config & 0xFFFFF) * (interleave * channel_count); /* in blocks */
|
|
|
|
|
|
|
|
|
|
|
|
/* build the VGMSTREAM */
|
|
|
|
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
|
|
|
if (!vgmstream) goto fail;
|
|
|
|
|
|
|
|
vgmstream->meta_type = meta_PSF;
|
|
|
|
vgmstream->sample_rate = sample_rate;
|
|
|
|
|
|
|
|
switch(codec) {
|
|
|
|
case coding_PSX:
|
|
|
|
vgmstream->coding_type = coding_PSX;
|
|
|
|
vgmstream->layout_type = layout_interleave;
|
|
|
|
vgmstream->interleave_block_size = interleave;
|
|
|
|
|
|
|
|
vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case coding_PSX_pivotal:
|
|
|
|
vgmstream->coding_type = coding_PSX_pivotal;
|
|
|
|
vgmstream->layout_type = layout_interleave;
|
|
|
|
vgmstream->interleave_block_size = interleave;
|
|
|
|
|
|
|
|
vgmstream->num_samples = ps_cfg_bytes_to_samples(data_size, 0x10, channel_count);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case coding_NGC_DSP:
|
|
|
|
vgmstream->coding_type = coding_NGC_DSP;
|
|
|
|
vgmstream->layout_type = layout_interleave;
|
|
|
|
vgmstream->interleave_block_size = interleave;
|
|
|
|
/* has standard DSP headers at 0x08 */
|
|
|
|
dsp_read_coefs_be(vgmstream,streamFile,0x08+0x1c,0x60);
|
|
|
|
dsp_read_hist_be (vgmstream,streamFile,0x08+0x40,0x60);
|
|
|
|
|
|
|
|
vgmstream->num_samples = read_32bitBE(0x08, streamFile);//dsp_bytes_to_samples(data_size, channel_count);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2019-08-15 15:12:13 +02:00
|
|
|
vgmstream->stream_size = data_size;
|
|
|
|
|
2019-08-11 19:38:40 +02:00
|
|
|
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
|
|
|
goto fail;
|
|
|
|
return vgmstream;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
close_vgmstream(vgmstream);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* PSF segmented - Pivotal games multiple segments (external in some PC/Xbox or inside bigfiles) [The Great Escape, Conflict series] */
|
|
|
|
VGMSTREAM * init_vgmstream_psf_segmented(STREAMFILE *streamFile) {
|
|
|
|
VGMSTREAM * vgmstream = NULL;
|
|
|
|
STREAMFILE* temp_streamFile = NULL;
|
|
|
|
segmented_layout_data *data = NULL;
|
|
|
|
int i, segment_count, loop_flag = 0, loop_start = 0, loop_end = 0;
|
|
|
|
off_t offset;
|
|
|
|
|
|
|
|
|
|
|
|
/* checks */
|
|
|
|
/* .psf: actual extension
|
|
|
|
* .swd: bigfile extension */
|
|
|
|
if (!check_extensions(streamFile, "psf,swd"))
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
if (read_32bitBE(0x00,streamFile) != 0x50534660 && /* "PSF\60" [The Great Escape (PC/Xbox/PS2), Conflict: Desert Storm (Xbox/GC)] */
|
2019-08-15 15:12:13 +02:00
|
|
|
read_32bitBE(0x00,streamFile) != 0x50534631) /* "PSF\31" [Conflict: Desert Storm 2 (Xbox/GC/PS2), Conflict: Global Terror (Xbox)] */
|
2019-08-11 19:38:40 +02:00
|
|
|
goto fail;
|
|
|
|
|
|
|
|
segment_count = read_32bitLE(0x04, streamFile);
|
|
|
|
loop_flag = 0;
|
|
|
|
|
|
|
|
offset = 0x08;
|
|
|
|
offset += 0x0c; /* first segment points to itself? */
|
|
|
|
segment_count--;
|
|
|
|
|
|
|
|
/* build segments */
|
|
|
|
data = init_layout_segmented(segment_count);
|
|
|
|
if (!data) goto fail;
|
|
|
|
|
|
|
|
for (i = 0; i < segment_count; i++) {
|
|
|
|
off_t psf_offset;
|
|
|
|
size_t psf_size;
|
|
|
|
|
|
|
|
/* mini table */
|
|
|
|
psf_offset = read_32bitLE(offset + 0x00, streamFile);
|
2019-08-15 15:12:13 +02:00
|
|
|
psf_size = get_streamfile_size(streamFile) - psf_offset; /* not ok but meh */
|
2019-08-11 19:38:40 +02:00
|
|
|
/* 0x04-0c: 0x02*4 transition segments (possibly to 4 song variations) */
|
|
|
|
|
|
|
|
/* use last section transition as loop */
|
|
|
|
if (i + 1 == segment_count) {
|
|
|
|
loop_flag = 1;
|
|
|
|
loop_start = read_16bitLE(offset + 0x0a, streamFile) - 1; /* also ignore first segment */
|
|
|
|
loop_end = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* multiple segment can point to the same PSF offset (for repeated song sections) */
|
2019-08-15 15:12:13 +02:00
|
|
|
//todo reuse repeated VGMSTREAMs to improve memory and bitrate calcs a bit
|
2019-08-11 19:38:40 +02:00
|
|
|
|
|
|
|
temp_streamFile = setup_subfile_streamfile(streamFile, psf_offset, psf_size, "psf");
|
|
|
|
if (!temp_streamFile) goto fail;
|
|
|
|
|
|
|
|
data->segments[i] = init_vgmstream_psf_single(temp_streamFile);
|
|
|
|
if (!data->segments[i]) goto fail;
|
|
|
|
|
|
|
|
offset += 0x0c;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* setup VGMSTREAMs */
|
|
|
|
if (!setup_layout_segmented(data))
|
|
|
|
goto fail;
|
|
|
|
vgmstream = allocate_segmented_vgmstream(data,loop_flag, loop_start, loop_end);
|
|
|
|
if (!vgmstream) goto fail;
|
|
|
|
|
|
|
|
return vgmstream;
|
|
|
|
fail:
|
|
|
|
if (!vgmstream) free_layout_segmented(data);
|
|
|
|
close_streamfile(temp_streamFile);
|
|
|
|
close_vgmstream(vgmstream);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-08-15 15:12:13 +02:00
|
|
|
/* ***************************************************** */
|
|
|
|
|
|
|
|
static VGMSTREAM * init_vgmstream_psf_pfsm(STREAMFILE *streamFile) {
|
2019-08-11 19:38:40 +02:00
|
|
|
VGMSTREAM * vgmstream = NULL;
|
|
|
|
off_t start_offset;
|
2019-08-15 15:12:13 +02:00
|
|
|
int loop_flag, channel_count, sample_rate = 0, rate_value = 0, interleave, big_endian;
|
|
|
|
size_t data_size;
|
|
|
|
coding_t codec;
|
|
|
|
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
|
|
|
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
|
|
|
|
|
|
|
|
/* standard:
|
|
|
|
* 0x00: -1/number (lang?)
|
|
|
|
* 0x04: config/size?
|
|
|
|
* 0x08: channel size? only ok for PSX-pivotal
|
|
|
|
* 0x0c: sample rate or rate_value
|
|
|
|
* 0x0e: 0x4=PSX-pivotal or 0xFF=PSX
|
|
|
|
* 0x0f: name size (0xCC/FF=null)
|
|
|
|
* 0x10: data
|
|
|
|
*
|
|
|
|
* GC is similar with 0x20-align between some fields
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* checks */
|
|
|
|
//if (!check_extensions(streamFile, "psf"))
|
|
|
|
// goto fail;
|
|
|
|
if (read_32bitBE(0x00,streamFile) != 0x5046534D && /* "PFSM" */
|
|
|
|
read_32bitLE(0x00,streamFile) != 0x5046534D) /* "PFSM" (BE) */
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
big_endian = (read_32bitLE(0x00,streamFile) == 0x5046534D);
|
|
|
|
if (big_endian) {
|
|
|
|
read_32bit = read_32bitBE;
|
|
|
|
read_16bit = read_16bitBE;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
read_32bit = read_32bitLE;
|
|
|
|
read_16bit = read_16bitLE;
|
|
|
|
}
|
|
|
|
|
|
|
|
loop_flag = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (big_endian && read_32bit(0x50, streamFile) != 0) { /* GC */
|
|
|
|
codec = coding_NGC_DSP;
|
|
|
|
interleave = 0x08;
|
|
|
|
channel_count = 1;
|
|
|
|
rate_value = (uint16_t)read_16bit(0x48, streamFile);
|
|
|
|
|
|
|
|
start_offset = 0x60 + 0x60 * channel_count;
|
|
|
|
}
|
|
|
|
else if (big_endian) { /* GC */
|
|
|
|
codec = coding_PCM16BE;
|
|
|
|
interleave = 0x02;
|
|
|
|
channel_count = 1;
|
|
|
|
rate_value = (uint16_t)read_16bit(0x48, streamFile);
|
|
|
|
|
|
|
|
start_offset = 0x60;
|
|
|
|
}
|
|
|
|
else if ((uint8_t)read_8bit(0x16, streamFile) == 0xFF) { /* PS2 */
|
|
|
|
codec = coding_PSX;
|
|
|
|
interleave = 0x10;
|
|
|
|
rate_value = (uint16_t)read_16bit(0x14, streamFile);
|
|
|
|
channel_count = 1;
|
|
|
|
|
|
|
|
start_offset = 0x18;
|
|
|
|
}
|
|
|
|
else { /* PC/Xbox, some PS2/GC */
|
|
|
|
codec = coding_PSX_pivotal;
|
|
|
|
interleave = 0x10;
|
|
|
|
sample_rate = (uint16_t)read_16bit(0x14, streamFile);
|
|
|
|
channel_count = 1;
|
|
|
|
|
|
|
|
start_offset = 0x18;
|
|
|
|
}
|
|
|
|
|
|
|
|
data_size = get_streamfile_size(streamFile) - start_offset;
|
|
|
|
|
|
|
|
/* pitch/cents? */
|
|
|
|
if (sample_rate == 0) {
|
|
|
|
/* pitch/cents? */
|
|
|
|
switch(rate_value) {
|
|
|
|
case 3763: sample_rate = 44100; break;
|
|
|
|
case 1365: sample_rate = 16000; break;
|
|
|
|
case 940: sample_rate = 11050; break;
|
|
|
|
case 460: sample_rate = 5000; break;
|
|
|
|
default:
|
|
|
|
VGM_LOG("PSF: unknown rate value %x\n", rate_value);
|
|
|
|
sample_rate = rate_value * 11.72; /* not exact but works well enough */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* build the VGMSTREAM */
|
|
|
|
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
|
|
|
if (!vgmstream) goto fail;
|
|
|
|
|
|
|
|
vgmstream->meta_type = meta_PSF;
|
|
|
|
vgmstream->sample_rate = sample_rate;
|
|
|
|
|
|
|
|
switch(codec) {
|
|
|
|
case coding_PCM16BE:
|
|
|
|
vgmstream->coding_type = coding_PCM16BE;
|
|
|
|
vgmstream->layout_type = layout_interleave;
|
|
|
|
vgmstream->interleave_block_size = interleave;
|
|
|
|
|
|
|
|
vgmstream->num_samples = pcm_bytes_to_samples(data_size, channel_count, 16);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case coding_PSX:
|
|
|
|
vgmstream->coding_type = coding_PSX;
|
|
|
|
vgmstream->layout_type = layout_interleave;
|
|
|
|
vgmstream->interleave_block_size = interleave;
|
|
|
|
|
|
|
|
vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case coding_PSX_pivotal:
|
|
|
|
vgmstream->coding_type = coding_PSX_pivotal;
|
|
|
|
vgmstream->layout_type = layout_interleave;
|
|
|
|
vgmstream->interleave_block_size = interleave;
|
|
|
|
|
|
|
|
vgmstream->num_samples = ps_cfg_bytes_to_samples(data_size, 0x10, channel_count);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case coding_NGC_DSP:
|
|
|
|
vgmstream->coding_type = coding_NGC_DSP;
|
|
|
|
vgmstream->layout_type = layout_interleave;
|
|
|
|
vgmstream->interleave_block_size = interleave;
|
|
|
|
/* has standard DSP headers at 0x08 */
|
|
|
|
dsp_read_coefs_be(vgmstream,streamFile,0x60+0x1c,0x60);
|
|
|
|
dsp_read_hist_be (vgmstream,streamFile,0x60+0x40,0x60);
|
|
|
|
|
|
|
|
vgmstream->num_samples = read_32bitBE(0x60, streamFile);//dsp_bytes_to_samples(data_size, channel_count);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
|
|
|
goto fail;
|
|
|
|
return vgmstream;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
close_vgmstream(vgmstream);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef enum { UNKNOWN, IMUS, PFST, PFSM } sch_type;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* SCH - Pivotal games multi-audio container [The Great Escape, Conflict series] */
|
|
|
|
VGMSTREAM * init_vgmstream_sch(STREAMFILE *streamFile) {
|
|
|
|
VGMSTREAM *vgmstream = NULL;
|
|
|
|
STREAMFILE *external_streamFile = NULL;
|
|
|
|
STREAMFILE *temp_streamFile = NULL;
|
|
|
|
off_t skip = 0, chunk_offset, target_offset = 0, header_offset, subfile_offset = 0;
|
|
|
|
size_t file_size, chunk_padding, target_size = 0, subfile_size = 0;
|
|
|
|
int big_endian;
|
|
|
|
int total_subsongs = 0, target_subsong = streamFile->stream_index;
|
|
|
|
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
|
|
|
sch_type target_type = UNKNOWN;
|
|
|
|
char stream_name[STREAM_NAME_SIZE] ={0};
|
2019-08-11 19:38:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* checks */
|
|
|
|
if (!check_extensions(streamFile, "sch"))
|
|
|
|
goto fail;
|
|
|
|
|
2019-08-15 15:12:13 +02:00
|
|
|
if (read_32bitBE(0x00,streamFile) == 0x48445253) /* "HDRSND" (found on later games) */
|
|
|
|
skip = 0x0E;
|
|
|
|
if (read_32bitBE(skip + 0x00,streamFile) != 0x53434800 && /* "SCH\0" */
|
|
|
|
read_32bitLE(skip + 0x00,streamFile) != 0x53434800) /* "SCH\0" (BE consoles) */
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
|
2019-08-11 19:38:40 +02:00
|
|
|
/* chunked format (id+size, GC pads to 0x20 and uses BE/inverted ids):
|
|
|
|
* no other info so total subsongs would be count of usable chunks
|
2019-08-15 15:12:13 +02:00
|
|
|
* (offsets are probably in level .dat files) */
|
|
|
|
big_endian = (read_32bitLE(skip + 0x00,streamFile) == 0x53434800);
|
|
|
|
if (big_endian) {
|
|
|
|
read_32bit = read_32bitBE;
|
|
|
|
chunk_padding = 0x18;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
read_32bit = read_32bitLE;
|
|
|
|
chunk_padding = 0;
|
|
|
|
}
|
2019-08-11 19:38:40 +02:00
|
|
|
|
2019-08-15 15:12:13 +02:00
|
|
|
file_size = get_streamfile_size(streamFile);
|
|
|
|
if (read_32bit(skip + 0x04,streamFile) + skip + 0x08 + chunk_padding < file_size) /* sometimes padded */
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
if (target_subsong == 0) target_subsong = 1;
|
|
|
|
|
|
|
|
chunk_offset = skip + 0x08 + chunk_padding;
|
|
|
|
|
|
|
|
/* get all files*/
|
|
|
|
while (chunk_offset < file_size) {
|
|
|
|
uint32_t chunk_id = read_32bitBE(chunk_offset + 0x00,streamFile);
|
|
|
|
uint32_t chunk_size = read_32bit(chunk_offset + 0x04,streamFile);
|
|
|
|
sch_type current_type = UNKNOWN;
|
|
|
|
|
|
|
|
switch(chunk_id) {
|
|
|
|
case 0x494D5553: /* "IMUS" (TGE PC/Xbox only) */
|
|
|
|
current_type = IMUS;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x54534650:
|
|
|
|
case 0x50465354: /* "PFST" */
|
|
|
|
current_type = PFST;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x4D534650:
|
|
|
|
case 0x5046534D: /* "PFSM" */
|
|
|
|
current_type = PFSM;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x4B4E4142:
|
|
|
|
case 0x42414E4B: /* "BANK" */
|
|
|
|
/* unknown format (variable size), maybe config for entry numbers */
|
|
|
|
break;
|
|
|
|
case 0x424C4F4B: /* "BLOK" [Conflict: Desert Storm (Xbox)] */
|
|
|
|
/* some ids or something? */
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
VGM_LOG("SCH: unknown chunk at %lx\n", chunk_offset);
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (current_type != UNKNOWN)
|
|
|
|
total_subsongs++;
|
|
|
|
|
|
|
|
if (total_subsongs == target_subsong && target_type == UNKNOWN) {
|
|
|
|
target_type = current_type;
|
|
|
|
target_offset = chunk_offset;
|
|
|
|
target_size = 0x08 + chunk_padding + chunk_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
chunk_offset += 0x08 + chunk_padding + chunk_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
|
|
|
if (target_size == 0) goto fail;
|
|
|
|
|
|
|
|
header_offset = target_offset + 0x08 + chunk_padding;
|
|
|
|
|
|
|
|
//;VGM_LOG("SCH: offset=%lx, size=%x\n",target_offset, target_size);
|
|
|
|
|
|
|
|
switch(target_type) {
|
|
|
|
case IMUS: { /* external segmented track */
|
|
|
|
STREAMFILE *psf_streamFile;
|
|
|
|
uint8_t name_size;
|
|
|
|
char name[255];
|
|
|
|
|
|
|
|
/* 0x00: config/size?
|
|
|
|
* 0x04: name size
|
|
|
|
* 0x05: segments
|
|
|
|
* 0x06: ?
|
|
|
|
* 0x08: relative path to .psf
|
|
|
|
* 0xNN: segment table (same as .psf)
|
|
|
|
*/
|
|
|
|
|
|
|
|
name_size = read_8bit(header_offset + 0x04, streamFile);
|
|
|
|
read_string(name,name_size, header_offset + 0x08, streamFile);
|
|
|
|
|
|
|
|
/* later games have name but actually use bigfile [Conflict: Global Storm (Xbox)] */
|
|
|
|
if ((uint8_t)read_8bit(header_offset + 0x07, streamFile) == 0xCC) {
|
|
|
|
external_streamFile = open_streamfile_by_filename(streamFile, "Stream.swd");
|
|
|
|
if (!external_streamFile) goto fail;
|
|
|
|
|
|
|
|
subfile_offset = read_32bit(header_offset + 0x08 + name_size, streamFile);
|
|
|
|
subfile_size = get_streamfile_size(external_streamFile) - subfile_offset; /* not ok but meh */
|
|
|
|
|
|
|
|
temp_streamFile = setup_subfile_streamfile(external_streamFile, subfile_offset,subfile_size, "psf");
|
|
|
|
if (!temp_streamFile) goto fail;
|
|
|
|
|
|
|
|
psf_streamFile = temp_streamFile;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
external_streamFile = open_streamfile_by_filename(streamFile, name);
|
|
|
|
if (!external_streamFile) goto fail;
|
|
|
|
|
|
|
|
psf_streamFile = external_streamFile;
|
|
|
|
}
|
|
|
|
|
|
|
|
vgmstream = init_vgmstream_psf_segmented(psf_streamFile);
|
|
|
|
if (!vgmstream) {
|
|
|
|
vgmstream = init_vgmstream_psf_single(psf_streamFile);
|
|
|
|
if (!vgmstream) goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(stream_name,sizeof(stream_name), "%s-%s" , "IMUS", name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case PFST: { /* external track */
|
|
|
|
STREAMFILE *psf_streamFile;
|
|
|
|
uint8_t name_size;
|
|
|
|
char name[255];
|
|
|
|
|
|
|
|
if (chunk_padding == 0 && target_size > 0x08 + 0x0c) { /* TGE PC/Xbox version */
|
|
|
|
/* 0x00: -1/0
|
|
|
|
* 0x04: config/size?
|
|
|
|
* 0x08: channel size
|
|
|
|
* 0x0c: sample rate? (differs vs PSF)
|
|
|
|
* 0x0e: 4?
|
|
|
|
* 0x0f: name size
|
|
|
|
* 0x10: name
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* later games have name but actually use bigfile [Conflict: Global Storm (Xbox)] */
|
|
|
|
if ((read_32bitBE(header_offset + 0x14, streamFile) & 0x0000FFFF) == 0xCCCC) {
|
|
|
|
name_size = read_8bit(header_offset + 0x13, streamFile);
|
|
|
|
read_string(name,name_size, header_offset + 0x18, streamFile);
|
|
|
|
|
|
|
|
external_streamFile = open_streamfile_by_filename(streamFile, "Stream.swd");
|
|
|
|
if (!external_streamFile) goto fail;
|
|
|
|
|
|
|
|
subfile_offset = read_32bit(header_offset + 0x0c, streamFile);
|
|
|
|
subfile_size = get_streamfile_size(external_streamFile) - subfile_offset; /* not ok but meh */
|
|
|
|
|
|
|
|
temp_streamFile = setup_subfile_streamfile(external_streamFile, subfile_offset,subfile_size, "psf");
|
|
|
|
if (!temp_streamFile) goto fail;
|
|
|
|
|
|
|
|
psf_streamFile = temp_streamFile;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
name_size = read_8bit(header_offset + 0x0f, streamFile);
|
|
|
|
read_string(name,name_size, header_offset + 0x10, streamFile);
|
|
|
|
|
|
|
|
external_streamFile = open_streamfile_by_filename(streamFile, name);
|
|
|
|
if (!external_streamFile) goto fail;
|
|
|
|
|
|
|
|
psf_streamFile = external_streamFile;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (chunk_padding) {
|
|
|
|
strcpy(name, "STREAM.SWD"); /* fixed */
|
|
|
|
|
|
|
|
/* 0x00: -1
|
|
|
|
* 0x04: config/size?
|
|
|
|
* 0x08: .swd offset
|
|
|
|
*/
|
|
|
|
external_streamFile = open_streamfile_by_filename(streamFile, name);
|
|
|
|
if (!external_streamFile) goto fail;
|
|
|
|
|
|
|
|
subfile_offset = read_32bit(header_offset + 0x24, streamFile);
|
|
|
|
subfile_size = get_streamfile_size(external_streamFile) - subfile_offset; /* not ok but meh */
|
|
|
|
|
|
|
|
temp_streamFile = setup_subfile_streamfile(external_streamFile, subfile_offset,subfile_size, "psf");
|
|
|
|
if (!temp_streamFile) goto fail;
|
|
|
|
|
|
|
|
psf_streamFile = temp_streamFile;
|
|
|
|
}
|
|
|
|
else { /* others */
|
|
|
|
strcpy(name, "STREAM.SWD"); /* fixed */
|
|
|
|
|
|
|
|
/* 0x00: -1
|
|
|
|
* 0x04: config/size?
|
|
|
|
* 0x08: .swd offset
|
|
|
|
*/
|
|
|
|
external_streamFile = open_streamfile_by_filename(streamFile, name);
|
|
|
|
if (!external_streamFile) goto fail;
|
|
|
|
|
|
|
|
subfile_offset = read_32bit(header_offset + 0x08, streamFile);
|
|
|
|
subfile_size = get_streamfile_size(external_streamFile) - subfile_offset; /* not ok but meh */
|
|
|
|
|
|
|
|
temp_streamFile = setup_subfile_streamfile(external_streamFile, subfile_offset,subfile_size, "psf");
|
|
|
|
if (!temp_streamFile) goto fail;
|
|
|
|
|
|
|
|
psf_streamFile = temp_streamFile;
|
|
|
|
}
|
|
|
|
|
|
|
|
vgmstream = init_vgmstream_psf_segmented(psf_streamFile);
|
|
|
|
if (!vgmstream) {
|
|
|
|
vgmstream = init_vgmstream_psf_single(psf_streamFile);
|
|
|
|
if (!vgmstream) goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(stream_name,sizeof(stream_name), "%s-%s" , "PFST", name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case PFSM:
|
|
|
|
/* internal sound */
|
|
|
|
|
|
|
|
temp_streamFile = setup_subfile_streamfile(streamFile, target_offset,target_size, NULL);
|
|
|
|
if (!temp_streamFile) goto fail;
|
|
|
|
|
|
|
|
vgmstream = init_vgmstream_psf_pfsm(temp_streamFile);
|
|
|
|
if (!vgmstream) goto fail;
|
|
|
|
|
|
|
|
snprintf(stream_name,sizeof(stream_name), "%s" , "PFSM");
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: /* target not found */
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
vgmstream->num_streams = total_subsongs;
|
|
|
|
strcpy(vgmstream->stream_name, stream_name);
|
|
|
|
|
|
|
|
close_streamfile(temp_streamFile);
|
|
|
|
close_streamfile(external_streamFile);
|
2019-08-11 19:38:40 +02:00
|
|
|
return vgmstream;
|
2019-08-15 15:12:13 +02:00
|
|
|
|
2019-08-11 19:38:40 +02:00
|
|
|
fail:
|
|
|
|
close_streamfile(temp_streamFile);
|
2019-08-15 15:12:13 +02:00
|
|
|
close_streamfile(external_streamFile);
|
2019-08-11 19:38:40 +02:00
|
|
|
close_vgmstream(vgmstream);
|
|
|
|
return NULL;
|
|
|
|
}
|