Merge pull request #345 from bnnm/schl-eacs-etc

schl eacs etc
This commit is contained in:
Christopher Snowhill 2019-01-02 16:42:59 -08:00 committed by GitHub
commit 9346ae50ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 657 additions and 160 deletions

View File

@ -226,8 +226,9 @@ single file). Contains dynamic text commands to read data from the original
file, or static values.
**TXTP**: a text playlist that works as a single song. Can contain a list of
filenames to play as one (ex. "intro.vag" "loop.vag"), name with subsong index
(ex. bgm.sxd#10), or mask channels to only play some (ex. "song.adx#c1,2").
filenames to play as one (ex. "intro.vag" "loop.vag"), separate channel files
to join as a single multichannel file, subsong index (ex. bgm.sxd#10), or
channel mask to allow only certain channels (ex. "song.adx#c1,2").
Creation of those files is meant for advanced users, docs can be found in
vgmstream source.
@ -297,6 +298,7 @@ are used in few games.
- Konami XMD 4-bit ADPCM
- Argonaut ASF 4-bit ADPCM
- Circus XPCM ADPCM
- PC-FX ADPCM
- SDX2 2:1 Squareroot-Delta-Exact compression DPCM
- CBD2 2:1 Cuberoot-Delta-Exact compression DPCM
- Activision EXAKT SASSC DPCM

View File

@ -30,7 +30,7 @@ static const int EA_XA_TABLE[20] = {
};
/* EA XA v2 (always mono); like ea_xa_int but with "PCM samples" flag and doesn't add 128 on expand or clamp (pre-adjusted by the encoder?) */
void decode_ea_xa_v2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel) {
void decode_ea_xa_v2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
uint8_t frame_info;
int32_t coef1, coef2;
int i, sample_count, shift;
@ -83,6 +83,106 @@ void decode_ea_xa_v2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspac
}
}
#if 0
/* later PC games use float math, though in the end sounds basically the same (decompiled from various exes) */
static const double XA_K0[16] = { 0.0, 0.9375, 1.796875, 1.53125 };
static const double XA_K1[16] = { 0.0, 0.0, -0.8125, -0.859375 };
/* code uses look-up table but it's be equivalent to:
* (double)((nibble << 28) >> (shift + 8) >> 8) or (double)(signed_nibble << (12 - shift)) */
static const uint32_t FLOAT_TABLE_INT[256] = {
0x00000000,0x45800000,0x46000000,0x46400000,0x46800000,0x46A00000,0x46C00000,0x46E00000,
0xC7000000,0xC6E00000,0xC6C00000,0xC6A00000,0xC6800000,0xC6400000,0xC6000000,0xC5800000,
0x00000000,0x45000000,0x45800000,0x45C00000,0x46000000,0x46200000,0x46400000,0x46600000,
0xC6800000,0xC6600000,0xC6400000,0xC6200000,0xC6000000,0xC5C00000,0xC5800000,0xC5000000,
0x00000000,0x44800000,0x45000000,0x45400000,0x45800000,0x45A00000,0x45C00000,0x45E00000,
0xC6000000,0xC5E00000,0xC5C00000,0xC5A00000,0xC5800000,0xC5400000,0xC5000000,0xC4800000,
0x00000000,0x44000000,0x44800000,0x44C00000,0x45000000,0x45200000,0x45400000,0x45600000,
0xC5800000,0xC5600000,0xC5400000,0xC5200000,0xC5000000,0xC4C00000,0xC4800000,0xC4000000,
0x00000000,0x43800000,0x44000000,0x44400000,0x44800000,0x44A00000,0x44C00000,0x44E00000,
0xC5000000,0xC4E00000,0xC4C00000,0xC4A00000,0xC4800000,0xC4400000,0xC4000000,0xC3800000,
0x00000000,0x43000000,0x43800000,0x43C00000,0x44000000,0x44200000,0x44400000,0x44600000,
0xC4800000,0xC4600000,0xC4400000,0xC4200000,0xC4000000,0xC3C00000,0xC3800000,0xC3000000,
0x00000000,0x42800000,0x43000000,0x43400000,0x43800000,0x43A00000,0x43C00000,0x43E00000,
0xC4000000,0xC3E00000,0xC3C00000,0xC3A00000,0xC3800000,0xC3400000,0xC3000000,0xC2800000,
0x00000000,0x42000000,0x42800000,0x42C00000,0x43000000,0x43200000,0x43400000,0x43600000,
0xC3800000,0xC3600000,0xC3400000,0xC3200000,0xC3000000,0xC2C00000,0xC2800000,0xC2000000,
0x00000000,0x41800000,0x42000000,0x42400000,0x42800000,0x42A00000,0x42C00000,0x42E00000,
0xC3000000,0xC2E00000,0xC2C00000,0xC2A00000,0xC2800000,0xC2400000,0xC2000000,0xC1800000,
0x00000000,0x41000000,0x41800000,0x41C00000,0x42000000,0x42200000,0x42400000,0x42600000,
0xC2800000,0xC2600000,0xC2400000,0xC2200000,0xC2000000,0xC1C00000,0xC1800000,0xC1000000,
0x00000000,0x40800000,0x41000000,0x41400000,0x41800000,0x41A00000,0x41C00000,0x41E00000,
0xC2000000,0xC1E00000,0xC1C00000,0xC1A00000,0xC1800000,0xC1400000,0xC1000000,0xC0800000,
0x00000000,0x40000000,0x40800000,0x40C00000,0x41000000,0x41200000,0x41400000,0x41600000,
0xC1800000,0xC1600000,0xC1400000,0xC1200000,0xC1000000,0xC0C00000,0xC0800000,0xC0000000,
0x00000000,0x3F800000,0x40000000,0x40400000,0x40800000,0x40A00000,0x40C00000,0x40E00000,
0xC1000000,0xC0E00000,0xC0C00000,0xC0A00000,0xC0800000,0xC0400000,0xC0000000,0xBF800000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
};
static const float *FLOAT_TABLE = (const float *)FLOAT_TABLE_INT;
void decode_ea_xa_v2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
uint8_t frame_info;
int i, sample_count, shift;
int pcm_frame_size = 0x01 + 2*0x02 + 28*0x02;
int xa_frame_size = 0x0f;
int frame_samples = 28;
first_sample = first_sample % frame_samples;
/* header */
frame_info = read_8bit(stream->offset,stream->streamfile);
if (frame_info == 0xEE) { /* PCM frame (used in later revisions), samples always BE */
stream->adpcm_history1_double = read_16bitBE(stream->offset + 0x01 + 0x00,stream->streamfile);
stream->adpcm_history2_double = read_16bitBE(stream->offset + 0x01 + 0x02,stream->streamfile);
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
outbuf[sample_count] = read_16bitBE(stream->offset + 0x01 + 2*0x02 + i*0x02,stream->streamfile);
}
/* only increment offset on complete frame */
if (i == frame_samples)
stream->offset += pcm_frame_size;
}
else { /* ADPCM frame */
double coef1, coef2, hist1, hist2, new_sample;
coef1 = XA_K0[(frame_info >> 4)];
coef2 = XA_K1[(frame_info >> 4)];
shift = (frame_info & 0x0F) + 8;// << 4;
hist1 = stream->adpcm_history1_double;
hist2 = stream->adpcm_history2_double;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
uint8_t sample_byte, sample_nibble;
off_t byte_offset = (stream->offset + 0x01 + i/2);
int nibble_shift = (!(i&1)) ? 4 : 0; /* high nibble first */
sample_byte = (uint8_t)read_8bit(byte_offset,stream->streamfile);
sample_nibble = (sample_byte >> nibble_shift) & 0x0F;
new_sample = (double)FLOAT_TABLE[sample_nibble + shift];
new_sample = new_sample + coef1 * hist1 + coef2 * hist2;
outbuf[sample_count] = clamp16((int)new_sample);
hist2 = hist1;
hist1 = new_sample;
}
stream->adpcm_history1_double = hist1;
stream->adpcm_history2_double = hist2;
/* only increment offset on complete frame */
if (i == frame_samples)
stream->offset += xa_frame_size;
}
}
#endif
/* EA XA v1 stereo */
void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel) {
uint8_t frame_info;

View File

@ -129,6 +129,7 @@ static const char* extension_list[] = {
"e4x",
"eam",
"eas",
"eda", //txth/reserved [Project Eden (PS2)]
"emff", //fake extension for .mul (to be removed)
"enm",
@ -156,10 +157,12 @@ static const char* extension_list[] = {
"genh",
"gms",
"gsb",
//"gsf", //conflicts with GBA gsf plugins?
"gtd",
"gwm",
"h4m",
"hab",
"hca",
"hdr",
"hgc1",
@ -417,8 +420,6 @@ static const char* extension_list[] = {
"thp",
"tk5",
"tra",
"trj",
"trm",
"tun",
"txth",
"txtp",
@ -466,6 +467,7 @@ static const char* extension_list[] = {
"wavebatch",
"wavm",
"wb",
"wd",
"wem",
"wii",
"wip", //txth/reserved [Colin McRae DiRT (PC)]
@ -751,7 +753,7 @@ static const meta_info meta_info_list[] = {
{meta_RWAV, "Nintendo RWAV header"},
{meta_CWAV, "Nintendo CWAV header"},
{meta_FWAV, "Nintendo FWAV header"},
{meta_XA, "Sony XA RIFF header"},
{meta_XA, "Sony XA header"},
{meta_PS2_RXWS, "Sony RXWS header"},
{meta_PS2_RAW, ".int PCM raw header"},
{meta_PS2_OMU, "Alter Echo OMU Header"},
@ -802,7 +804,8 @@ static const meta_info meta_info_list[] = {
{meta_HGC1, "Knights of the Temple 2 hgC1 Header"},
{meta_AUS, "Capcom AUS Header"},
{meta_RWS, "RenderWare RWS header"},
{meta_EA_1SNH, "Electronic Arts 1SNh/EACS header"},
{meta_EA_1SNH, "Electronic Arts 1SNh header"},
{meta_EA_EACS, "Electronic Arts EACS header"},
{meta_SL3, "Atari Melbourne House SL3 header"},
{meta_FSB1, "FMOD Sample Bank (FSB1) Header"},
{meta_FSB2, "FMOD Sample Bank (FSB2) Header"},
@ -842,6 +845,9 @@ static const meta_info meta_info_list[] = {
{meta_PS2_RKV, "Legacy of Kain - Blood Omen 2 RKV PS2 header"},
{meta_PS2_VAS, "Pro Baseball Spirits 5 VAS Header"},
{meta_PS2_TEC, "assumed TECMO badflagged stream by .tec extension"},
{meta_PS2_ENTH, ".enth Header"},
{meta_SDT, "High Voltage .sdt header"},
{meta_NGC_TYDSP, ".tydsp Header"},
{meta_XBOX_WVS, "Metal Arms WVS Header (XBOX)"},
{meta_NGC_WVS, "Metal Arms WVS Header (GameCube)"},
{meta_XBOX_MATX, "assumed Matrix file by .matx extension"},

View File

@ -1,7 +1,7 @@
#include "meta.h"
#include "../coding/coding.h"
typedef enum { ATRAC3, ATRAC9, KOVS, KTSS } atsl_codec;
typedef enum { ATRAC3, ATRAC9, KOVS, KTSS, KTAC } atsl_codec;
/* .ATSL - Koei Tecmo audio container [One Piece Pirate Warriors (PS3), Warriors All-Stars (PC)] */
VGMSTREAM * init_vgmstream_atsl(STREAMFILE *streamFile) {
@ -12,7 +12,7 @@ VGMSTREAM * init_vgmstream_atsl(STREAMFILE *streamFile) {
atsl_codec codec;
const char* fake_ext;
off_t subfile_offset = 0;
size_t subfile_size = 0, header_size;
size_t subfile_size = 0, header_size, entry_size;
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
@ -39,24 +39,31 @@ VGMSTREAM * init_vgmstream_atsl(STREAMFILE *streamFile) {
* - 00060301 00010301 atsl in G1L from One Piece Pirate Warriors 3 (PC)[KOVS]
* - 000A0301 00010501 atsl in G1L from Warriors All-Stars (PC)[KOVS]
* - 000B0301 00080601 atsl in G1l from Sengoku Musou Sanada Maru (Switch)[KTSS]
* - 010C0301 01060601 .atsl from Dynasty Warriors 9 (PS4)[KTAC]
*/
type = read_8bit(0x0d, streamFile);
entry_size = 0x28;
type = read_16bitLE(0x0c, streamFile);
switch(type) {
case 0x01:
case 0x0100:
codec = KOVS;
fake_ext = "kvs";
break;
case 0x02:
case 0x0200:
codec = ATRAC3;
fake_ext = "at3";
big_endian = 1;
break;
case 0x04:
case 0x06:
case 0x0400:
case 0x0600:
codec = ATRAC9;
fake_ext = "at9";
break;
case 0x08:
case 0x0601:
codec = KTAC;
fake_ext = "ktac";
entry_size = 0x3c;
break;
case 0x0800:
codec = KTSS;
fake_ext = "ktss";
break;
@ -78,14 +85,15 @@ VGMSTREAM * init_vgmstream_atsl(STREAMFILE *streamFile) {
for (i = 0; i < entries; i++) {
int is_unique = 1;
off_t entry_offset = read_32bit(header_size + i*0x28 + 0x04,streamFile);
size_t entry_size = read_32bit(header_size + i*0x28 + 0x08,streamFile);
/* 0x00: id?, 0x08+: sample rate/num_samples/loop_start/etc, matching subfile header */
/* 0x00: id */
off_t entry_subfile_offset = read_32bit(header_size + i*entry_size + 0x04,streamFile);
size_t entry_subfile_size = read_32bit(header_size + i*entry_size + 0x08,streamFile);
/* 0x08+: channels/sample rate/num_samples/loop_start/etc (match subfile header) */
/* check if current entry was repeated in a prev entry */
for (j = 0; j < i; j++) {
off_t prev_offset = read_32bit(header_size + j*0x28 + 0x04,streamFile);
if (prev_offset == entry_offset) {
off_t prev_offset = read_32bit(header_size + j*entry_size + 0x04,streamFile);
if (prev_offset == entry_subfile_offset) {
is_unique = 0;
break;
}
@ -97,8 +105,8 @@ VGMSTREAM * init_vgmstream_atsl(STREAMFILE *streamFile) {
/* target GET, but keep going to count subsongs */
if (!subfile_offset && target_subsong == total_subsongs) {
subfile_offset = entry_offset;
subfile_size = entry_size;
subfile_offset = entry_subfile_offset;
subfile_size = entry_subfile_size;
}
}
}
@ -129,6 +137,10 @@ VGMSTREAM * init_vgmstream_atsl(STREAMFILE *streamFile) {
vgmstream = init_vgmstream_ktss(temp_streamFile);
if (!vgmstream) goto fail;
break;
case KTAC:
//vgmstream = init_vgmstream_ktac(temp_streamFile); //Koei Tecto VBR-like ATRAC9
//if (!vgmstream) goto fail;
//break;
default:
goto fail;
}

View File

@ -17,27 +17,33 @@ typedef struct {
int32_t loop_start;
int32_t loop_end;
int32_t loop_start_offset;
int32_t data_offset;
int big_endian;
int loop_flag;
int is_sead;
int codec_config;
int is_bank;
int total_subsongs;
} ea_header;
static int parse_header(STREAMFILE* streamFile, ea_header* ea, off_t begin_offset);
static VGMSTREAM * init_vgmstream_main(STREAMFILE *streamFile, ea_header* ea);
static void set_ea_1snh_num_samples(STREAMFILE* streamFile, off_t start_offset, ea_header* ea);
static int get_ea_1snh_ima_version(STREAMFILE* streamFile, off_t start_offset, const ea_header* ea);
/* EA 1SNh - from early EA games (~1996, ex. Need for Speed) */
/* EA 1SNh - from early EA games, stream (~1996, ex. Need for Speed) */
VGMSTREAM * init_vgmstream_ea_1snh(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
ea_header ea = {0};
off_t eacs_offset;
/* checks */
/* .asf/as4: common, cnk: some PS games, .sng: fake for plugins (to mimic EA SCHl's common extension) */
/* .uv, .tgq: some SAT games */
/* .asf/as4: common
* .cnk: some PS games
* .sng: fake for plugins (to mimic EA SCHl's common extension)
* .uv/tgq: some SAT games (video only?) */
if (!check_extensions(streamFile,"asf,as4,cnk,sng,uv,tgq"))
goto fail;
@ -47,45 +53,117 @@ VGMSTREAM * init_vgmstream_ea_1snh(STREAMFILE *streamFile) {
/* stream is divided into blocks/chunks: 1SNh=audio header, 1SNd=data xN, 1SNl=loop end, 1SNe=end.
* Video uses various blocks (kVGT/fVGT/etc) and sometimes alt audio blocks (SEAD/SNDC/SEND). */
ea.is_sead = read_32bitBE(0x00,streamFile) == 0x53454144;
/* use block size as endian marker (Saturn = BE) */
ea.big_endian = !(read_32bitLE(0x04,streamFile) < 0x0000FFFF);
ea.big_endian = guess_endianness32bit(0x04,streamFile);
if (!parse_header(streamFile,&ea, 0x08))
eacs_offset = 0x08; /* after 1SNh block id+size */
if (!parse_header(streamFile,&ea, eacs_offset))
goto fail;
return init_vgmstream_main(streamFile, &ea);
fail:
return NULL;
}
/* EA EACS - from early EA games, bank (~1996, ex. Need for Speed) */
VGMSTREAM * init_vgmstream_ea_eacs(STREAMFILE *streamFile) {
ea_header ea = {0};
off_t eacs_offset;
/* checks */
/* .eas: single bank [Need for Speed (PC)]
* .bnk: multi bank [Need for Speed (PC)] */
if (!check_extensions(streamFile,"eas,bnk"))
goto fail;
start_offset = 0x00;
/* plain data without blocks, can contain N*(EACS header) + N*(data), or N (EACS header + data) */
ea.is_bank = 1;
/* use ??? as endian marker (Saturn = BE) */
//ea.big_endian = guess_endianness32bit(0x04,streamFile);
if (read_32bitBE(0x00,streamFile) == 0x45414353) { /* "EACS" */
/* single bank variant */
eacs_offset = 0x00;
}
else if (read_32bitBE(0x00,streamFile) == 0x00) {
/* multi bank variant */
int i;
int target_subsong = streamFile->stream_index;
if (target_subsong == 0) target_subsong = 1;
if (target_subsong < 0) goto fail;
/* offsets to EACSs are scattered in the first 0x200
* this looks dumb but seems like the only way */
eacs_offset = 0;
for (i = 0x00; i < 0x200; i += 0x04) {
off_t bank_offset = read_32bitLE(i, streamFile);
if (bank_offset == 0)
continue;
ea.total_subsongs++;
/* parse mini bank header */
if (ea.total_subsongs == target_subsong) {
/* 0x00: some id or flags? */
eacs_offset = read_32bitLE(bank_offset + 0x04, streamFile);
/* rest: not sure if part of this header */
}
}
if (eacs_offset == 0)
goto fail;
}
else {
goto fail;
}
if (!parse_header(streamFile,&ea, eacs_offset))
goto fail;
return init_vgmstream_main(streamFile, &ea);
fail:
return NULL;
}
static VGMSTREAM * init_vgmstream_main(STREAMFILE *streamFile, ea_header* ea) {
VGMSTREAM * vgmstream = NULL;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(ea.channels, ea.loop_flag);
vgmstream = allocate_vgmstream(ea->channels, ea->loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = ea.sample_rate;
vgmstream->num_samples = ea.num_samples;
vgmstream->loop_start_sample = ea.loop_start;
vgmstream->loop_end_sample = ea.loop_end;
vgmstream->sample_rate = ea->sample_rate;
vgmstream->num_samples = ea->num_samples;
vgmstream->loop_start_sample = ea->loop_start;
vgmstream->loop_end_sample = ea->loop_end;
vgmstream->codec_endian = ea.big_endian;
vgmstream->layout_type = layout_blocked_ea_1snh;
vgmstream->meta_type = meta_EA_1SNH;
vgmstream->codec_endian = ea->big_endian;
vgmstream->layout_type = ea->is_bank ? layout_none : layout_blocked_ea_1snh;
vgmstream->meta_type = ea->is_bank ? meta_EA_EACS : meta_EA_1SNH;
vgmstream->num_streams = ea->total_subsongs;
switch (ea.codec) {
switch (ea->codec) {
case EA_CODEC_PCM: /* Need for Speed (PC) */
vgmstream->coding_type = ea.bits==1 ? coding_PCM8_int : coding_PCM16_int;
vgmstream->coding_type = ea->bits==1 ? coding_PCM8_int : coding_PCM16_int;
break;
case EA_CODEC_ULAW: /* Crusader: No Remorse movies (SAT), FIFA 96 movies (SAT) */
if (ea.bits && ea.bits!=2) goto fail; /* only set in EACS */
if (ea->bits && ea->bits != 2) goto fail; /* only set in EACS */
vgmstream->coding_type = coding_ULAW_int;
break;
case EA_CODEC_IMA: /* Need for Speed II (PC) */
if (ea.bits && ea.bits!=2) goto fail; /* only in EACS */
if (ea->bits && ea->bits != 2) goto fail; /* only in EACS */
vgmstream->coding_type = coding_DVI_IMA; /* stereo/mono, high nibble first */
vgmstream->codec_config = ea.codec_config;
vgmstream->codec_config = ea->codec_config;
break;
case EA_CODEC_PSX: /* Need for Speed (PS) */
@ -93,12 +171,12 @@ VGMSTREAM * init_vgmstream_ea_1snh(STREAMFILE *streamFile) {
break;
default:
VGM_LOG("EA: unknown codec 0x%02x\n", ea.codec);
VGM_LOG("EA EACS: unknown codec 0x%02x\n", ea->codec);
goto fail;
}
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
if (!vgmstream_open_stream(vgmstream,streamFile,ea->data_offset))
goto fail;
return vgmstream;
@ -116,15 +194,18 @@ static int parse_header(STREAMFILE* streamFile, ea_header* ea, off_t offset) {
ea->bits = read_8bit(offset+0x08, streamFile);
ea->channels = read_8bit(offset+0x09, streamFile);
ea->codec = read_8bit(offset+0x0a, streamFile);
ea->type = read_8bit(offset+0x0b, streamFile);
ea->type = read_8bit(offset+0x0b, streamFile); /* block type? 0=1SNh, -1=bank */
ea->num_samples = read_32bit(offset+0x0c, streamFile);
ea->loop_start = read_32bit(offset+0x10, streamFile);
ea->loop_end = read_32bit(offset+0x14, streamFile) + ea->loop_start; /* loop length */
/* 0x18: data start? (0x00), 0x1c: pan/volume/etc? (0x7F), rest can be padding/garbage */
VGM_ASSERT(ea->type != 0, "EA EACS: unknown type\n"); /* block type? */
ea->data_offset = read_32bit(offset+0x18, streamFile); /* 0 when blocked */
/* 0x1c: pan/volume/etc? (0x7F)
* rest may be padding/garbage */
//VGM_ASSERT(ea->type != 0, "EA EACS: unknown type %i\n", ea->type);
if (ea->codec == EA_CODEC_IMA)
ea->codec_config = get_ea_1snh_ima_version(streamFile, 0x00, ea);
/* EACS banks with empty values exist but will be rejected later */
}
else if (ea->is_sead) {
/* alt subheader (found in some PC videos) */

View File

@ -5,24 +5,24 @@
/* header version */
#define EA_VERSION_NONE -1
#define EA_VERSION_V0 0x00 // ~early PC (when codec1 was used)
#define EA_VERSION_V1 0x01 // ~PC
#define EA_VERSION_V2 0x02 // ~PS era
#define EA_VERSION_V3 0x03 // ~PS2 era
#define EA_VERSION_V0 0x00 /* ~early PC (when codec1 was used) */
#define EA_VERSION_V1 0x01 /* ~PC */
#define EA_VERSION_V2 0x02 /* ~PS1 */
#define EA_VERSION_V3 0x03 /* ~PS2 */
/* platform constants (unasigned values seem internal only) */
#define EA_PLATFORM_GENERIC -1 // typically Wii/X360/PS3/videos
/* platform constants (unassigned values seem internal only) */
#define EA_PLATFORM_GENERIC -1 /* typically Wii/X360/PS3/videos */
#define EA_PLATFORM_PC 0x00
#define EA_PLATFORM_PSX 0x01
#define EA_PLATFORM_N64 0x02
#define EA_PLATFORM_MAC 0x03
#define EA_PLATFORM_SAT 0x04
#define EA_PLATFORM_PS2 0x05
#define EA_PLATFORM_GC_WII 0x06 // reused later for Wii
#define EA_PLATFORM_GC_WII 0x06
#define EA_PLATFORM_XBOX 0x07
#define EA_PLATFORM_X360 0x09 // also "Xenon"
#define EA_PLATFORM_X360 0x09
#define EA_PLATFORM_PSP 0x0A
#define EA_PLATFORM_PS3 0x0E // very rare [Need for Speed: Carbon (PS3)]
#define EA_PLATFORM_PS3 0x0E /* very rare [Need for Speed: Carbon (PS3)] */
#define EA_PLATFORM_3DS 0x14
/* codec constants (undefined are probably reserved, ie.- sx.exe encodes PCM24/DVI but no platform decodes them) */
@ -112,9 +112,24 @@ static void update_ea_stream_size_and_samples(STREAMFILE* streamFile, off_t star
/* EA SCHl with variable header - from EA games (roughly 1997~2010); generated by EA Canada's sx.exe/Sound eXchange */
VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) {
/* check extension; exts don't seem enforced by EA's tools, but usually:
* STR/ASF/MUS ~early, EAM ~mid, SNG/AUD ~late, rest uncommon/one game (ex. STRM: MySims Kingdom Wii) */
if (!check_extensions(streamFile,"str,asf,mus,eam,sng,aud,sx,strm,xa,xsf,exa,stm,trj,trm"))
/* check extension */
/* they don't seem enforced by EA's tools but usually:
* .asf: ~early [ex. Need for Speed (PC)]
* .str: ~early [ex. FIFA 2002 (PS1)]
* .eam: ~mid (fake?)
* .exa: ~mid [ex. 007 - From Russia with Love]
* .sng: ~late (fake?)
* .aud: ~late [ex. FIFA 14 (3DS)]
* .strm: MySims Kingdom (Wii)
* .stm: FIFA 12 (3DS)
* .sx/xa: fake?
* .hab: GoldenEye - Rogue Agent (inside .big)
* .xsf: 007 - Agent Under Fire (Xbox)
* .gsf: 007 - Everything or Nothing (GC)
* .mus: map/mpf+mus only?
* (extensionless): SSX (PS2) (inside .big) */
if (!check_extensions(streamFile,"asf,str,eam,exa,sng,aud,sx,xa,strm,stm,hab,xsf,gsf,mus,"))
goto fail;
/* check header */
@ -147,8 +162,12 @@ VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE *streamFile) {
off_t offset;
/* check extension */
/* .bnk: sfx, .sdt: speech, .mus: streams/jingles (rare) */
if (!check_extensions(streamFile,"bnk,sdt,mus"))
/* .bnk: common
* .sdt: Harry Potter games
* .mus: streams/jingles (rare)
* .abk: GoldenEye - Rogue Agent
* .ast: FIFA 2004 (inside .big) */
if (!check_extensions(streamFile,"bnk,sdt,mus,abk,ast"))
goto fail;
/* check header (doesn't use EA blocks, otherwise very similar to SCHl) */
@ -363,7 +382,59 @@ fail:
return NULL;
}
/* EA MAP/MUS combo - used in some old games for interactive music info */
/* open map/mpf+mus pairs that aren't exact pairs, since EA's games can load any combo */
static STREAMFILE * open_mapfile_pair(STREAMFILE *streamFile) {
static const char *const mapfile_pairs[][2] = {
/* standard cases, replace map part with mus part (from the end to preserve prefixes) */
{"MUS_CTRL.MPF","MUS_STR.MUS"}, /* GoldenEye - Rogue Agent (PS2) */
{"mus_ctrl.mpf","mus_str.mus"}, /* GoldenEye - Rogue Agent (others) */
{".mpf","_main.mus"}, /* 007 - Everything or Nothing (GC) */
/* hack when when multiple maps point to the same mus, uses name before "+"
* ex. ZZZTR00A.TRJ+ZTR00PGR.MAP or ZZZTR00A.TRJ+ZTR00R0A.MAP both point to ZZZTR00A.TRJ */
{"+",""}, /* Need for Speed III (PS1) */
};
STREAMFILE *musFile = NULL;
char file_name[PATH_LIMIT];
int pair_count = (sizeof(mapfile_pairs)/sizeof(mapfile_pairs[0]));
int i;
size_t file_len, map_len;
get_streamfile_filename(streamFile, file_name, PATH_LIMIT);
file_len = strlen(file_name);
for (i = 0; i < pair_count; i++) {
const char *map_name = mapfile_pairs[i][0];
const char *mus_name = mapfile_pairs[i][1];
map_len = strlen(map_name);
if (map_name[0] == '+') {
/* use name before "+" */
char *mod_name = strchr(file_name, '+');
if (mod_name == NULL)
continue;
mod_name[0] = '\0';
}
else {
/* replace map_name with expected mus_name */
if (file_len < map_len)
continue;
if (strncmp(file_name+(file_len - map_len), map_name, map_len) != 0)
continue;
file_name[file_len - map_len] = '\0';
strcat(file_name, mus_name);
}
musFile = open_streamfile_by_filename(streamFile, file_name);
if (musFile) return musFile;
get_streamfile_filename(streamFile, file_name, PATH_LIMIT); /* reset for next loop */
}
return NULL;
}
/* EA MAP/MUS combo - used in older games for interactive music (for EA's PathFinder tool) */
VGMSTREAM * init_vgmstream_ea_map_mus(STREAMFILE *streamFile) {
uint8_t version, num_sounds, num_userdata, userdata_size;
off_t section_offset, schl_offset;
@ -383,7 +454,10 @@ VGMSTREAM * init_vgmstream_ea_map_mus(STREAMFILE *streamFile) {
if (version > 1) goto fail;
musFile = open_streamfile_by_ext(streamFile, "mus");
if (!musFile) goto fail;
if (!musFile) {
musFile = open_mapfile_pair(streamFile);
if (!musFile) goto fail;
}
/*
* 0x04: ???
@ -427,7 +501,7 @@ fail:
return NULL;
}
/* EA MPF/MUS combo - used in newer 6th gen games for storing music */
/* EA MPF/MUS combo - used in newer 6th gen games for interactive music (for EA's PathFinder tool) */
VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE *streamFile) {
off_t section_offset, entry_offset, subentry_num, eof_offset, off_mult, schl_offset;
size_t sec2_size;
@ -463,12 +537,15 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE *streamFile) {
if (version == 5 && sub_version > 2) goto fail; /* newer version using SNR/SNS */
musFile = open_streamfile_by_ext(streamFile, "mus");
if (!musFile) goto fail;
if (!musFile) {
musFile = open_mapfile_pair(streamFile);
if (!musFile) goto fail;
}
/* HACK: number of sub-entries is stored in bitstreams that are different in LE and BE */
/* I can't figure it out, so let's just use a workaround for now */
if (version == 3 && sub_version == 1) { /* SSX Tricky /*
if (version == 3 && sub_version == 1) { /* SSX Tricky */
/* we need to go through the first two sections to find sound table */
sec1_num = read_16bit(0x12, streamFile);
sec2_size = read_8bit(0x0e, streamFile);
@ -517,7 +594,7 @@ VGMSTREAM * init_vgmstream_ea_mpf_mus(STREAMFILE *streamFile) {
eof_offset = read_32bit(entry_offset + 0x04, streamFile) * 0x04;
total_streams = (eof_offset - section_offset) / 0x08;
off_mult = 0x04;
} else if (version == 4) { /* SSX 3, Need for Speed: Underground 2 /*
} else if (version == 4) { /* SSX 3, Need for Speed: Underground 2 */
/* we need to go through the first two sections to find sound table */
sec1_num = read_16bit(0x12, streamFile);
sec2_num = read_8bit(0x0f, streamFile);

View File

@ -174,6 +174,7 @@ VGMSTREAM * init_vgmstream_pos(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_nwa(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ea_1snh(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_ea_eacs(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_xss(STREAMFILE * streamFile);

View File

@ -274,8 +274,9 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
* .xss: Spider-Man The Movie (Xbox)
* .xsew: Mega Man X Legacy Collections (PC)
* .adpcm: Angry Birds Transformers (Android)
* .adw: Dead Rising 2 (PC) */
if ( check_extensions(streamFile, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw") ) {
* .adw: Dead Rising 2 (PC)
* .wd: Genma Onimusha (Xbox) voices */
if ( check_extensions(streamFile, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd") ) {
;
}
else if ( check_extensions(streamFile, "mwv") ) {
@ -312,13 +313,13 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
/* some Dreamcast/Naomi games do this [Headhunter (DC), Bomber hehhe (DC)] */
if (riff_size + 0x04 == file_size && read_16bitLE(0x14,streamFile)==0x0000)
riff_size -= 0x04;
/* some PC games do this [Halo 2 (PC)] */
/* some PC games do this [Halo 2 (PC)] (possibly bad extractor? 'Gravemind Tool') */
if (riff_size + 0x04 == file_size && read_16bitLE(0x14,streamFile)==0x0069)
riff_size -= 0x04;
/* check for truncated RIFF */
if (file_size < riff_size+0x08) goto fail;
if (file_size < riff_size+0x08)
goto fail;
/* read through chunks to verify format and find metadata */
{

View File

@ -1,80 +1,58 @@
#include "meta.h"
#include "../util.h"
#include "../coding/coding.h"
/* SDT (Baldur's Gate - Dark Alliance) */
/* .sdt - from High Voltage games? [Baldur's Gate - Dark Alliance (GC)] */
VGMSTREAM * init_vgmstream_sdt(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
off_t start_offset;
size_t data_size;
int loop_flag, channel_count;
int loop_flag;
int channel_count;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("sdt",filename_extension(filename))) goto fail;
#if 0
/* check header */
if (read_32bitBE(0x00,streamFile) != 0x53565300) /* "SVS\0" */
/* checks */
if (!check_extensions(streamFile, "sdt"))
goto fail;
#endif
loop_flag = (read_32bitBE(0x04,streamFile)!=0);
channel_count = 2;
channel_count = read_32bitBE(0x00,streamFile); /* assumed */
loop_flag = (read_32bitBE(0x04,streamFile) != 0);
start_offset = 0xA0;
data_size = get_streamfile_size(streamFile) - start_offset;
/* build the VGMSTREAM */
if (channel_count != 2)
goto fail; /* only seen this */
if (read_32bitBE(0x08,streamFile) != read_32bitBE(0x08,streamFile))
goto fail; /* sample rate agreement between channels */
if (read_32bitBE(0x98,streamFile) != 0x8000)
goto fail; /* expected interleave */
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
start_offset = 0xA0;
vgmstream->channels = channel_count; /*read_32bitBE(0x00,streamFile);*/
vgmstream->meta_type = meta_SDT;
vgmstream->sample_rate = read_32bitBE(0x08,streamFile);
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->num_samples = read_32bitBE(0x14,streamFile)/8*14/channel_count;
vgmstream->num_samples = dsp_bytes_to_samples(data_size, channel_count); /* maybe at @0x14 - 2? */
if (loop_flag) {
vgmstream->loop_start_sample = 0; /* (read_32bitLE(0x08,streamFile)-1)*28; */
vgmstream->loop_end_sample = read_32bitBE(0x14,streamFile)/8*14/channel_count;
vgmstream->loop_start_sample = 0; /* maybe @0x08? */
vgmstream->loop_end_sample = vgmstream->num_samples; /* maybe @0x10? */
}
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x8000;
if (vgmstream->interleave_block_size)
vgmstream->interleave_last_block_size =
(data_size % (vgmstream->interleave_block_size*vgmstream->channels)) / vgmstream->channels;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x8000;
vgmstream->meta_type = meta_SDT;
dsp_read_coefs_be(vgmstream,streamFile, 0x3c,0x2E);
dsp_read_hist_be(vgmstream, streamFile, 0x60,0x2E);
if (vgmstream->coding_type == coding_NGC_DSP) {
int i;
for (i=0;i<16;i++) {
vgmstream->ch[0].adpcm_coef[i] = read_16bitBE(0x3C+i*2,streamFile);
}
if (vgmstream->channels) {
for (i=0;i<16;i++) {
vgmstream->ch[1].adpcm_coef[i] = read_16bitBE(0x6A+i*2,streamFile);
}
}
}
/* 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;i<channel_count;i++) {
vgmstream->ch[i].streamfile = file;
vgmstream->ch[i].channel_start_offset=
vgmstream->ch[i].offset=start_offset+
vgmstream->interleave_block_size*i;
}
}
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail;
return vgmstream;
/* clean up anything we may have opened */
fail:
if (vgmstream) close_vgmstream(vgmstream);
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -265,6 +265,8 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) {
size_t index_size, index_header_size;
off_t bao_offset, resources_offset;
int target_subsong = streamFile->stream_index;
uint8_t *index_buffer = NULL;
STREAMFILE *streamTest = NULL;
/* class: 0x01=index, 0x02=BAO */
@ -273,7 +275,7 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) {
/* index and resources always LE */
/* 0x01(3): version, major/minor/release (numbering continues from .sb0/sm0) */
index_size = read_32bitLE(0x04, streamFile); /* can be 0 */
index_size = read_32bitLE(0x04, streamFile); /* can be 0, not including */
resources_offset = read_32bitLE(0x08, streamFile); /* always found even if not used */
/* 0x0c: always 0? */
/* 0x10: unknown, null if no entries */
@ -287,14 +289,26 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) {
index_entries = index_size / 0x08;
index_header_size = 0x40;
/* parse index to get target subsong N = Nth header BAO */
/* pre-load to avoid too much I/O back and forth */
if (index_size > (10000*0x08)) {
VGM_LOG("BAO: index too big\n");
goto fail;
}
index_buffer = malloc(index_size);
read_streamfile(index_buffer, index_header_size, index_size, streamFile);
/* use smaller I/O buffer for performance, as this read lots of small BAO headers all over the place */
streamTest = reopen_streamfile(streamFile, 0x100);
if (!streamTest) goto fail;
/* parse index to get target subsong N = Nth audio header BAO */
bao_offset = index_header_size + index_size;
for (i = 0; i < index_entries; i++) {
//uint32_t bao_id = read_32bitLE(index_header_size+0x08*i+0x00, streamFile);
size_t bao_size = read_32bitLE(index_header_size+0x08*i+0x04, streamFile);
//uint32_t bao_id = get_32bitLE(index_buffer + 0x08*i+ 0x00);
size_t bao_size = get_32bitLE(index_buffer + 0x08*i + 0x04);
/* parse and continue to find out total_subsongs */
if (!parse_bao(bao, streamFile, bao_offset))
if (!parse_bao(bao, streamTest, bao_offset))
goto fail;
bao_offset += bao_size; /* files simply concat BAOs */
@ -393,8 +407,12 @@ static int parse_pk_header(ubi_bao_header * bao, STREAMFILE *streamFile) {
;VGM_LOG("BAO stream: id=%x, offset=%x, size=%x, res=%s\n", bao->stream_id, (uint32_t)bao->stream_offset, bao->stream_size, (bao->is_external ? bao->resource_name : "internal"));
free(index_buffer);
close_streamfile(streamTest);
return 1;
fail:
free(index_buffer);
close_streamfile(streamTest);
return 0;
}
@ -407,14 +425,14 @@ static int parse_bao(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offset)
/* 0x00(1): class? usually 0x02 but older BAOs have 0x01 too */
bao_version = read_32bitBE(offset + 0x00, streamFile) & 0x00FFFFFF;
bao_version = read_32bitBE(offset+0x00, streamFile) & 0x00FFFFFF;
/* detect endianness */
if (read_32bitLE(offset+0x04, streamFile) < 0x0000FFFF) {
read_32bit = read_32bitLE;
} else {
/* this could be done once as all BAOs share endianness */
if (guess_endianness32bit(offset+0x04, streamFile)) {
read_32bit = read_32bitBE;
bao->big_endian = 1;
} else {
read_32bit = read_32bitLE;
}
header_size = read_32bit(offset+0x04, streamFile); /* mainly 0x28, rarely 0x24 */

View File

@ -82,7 +82,7 @@ typedef struct {
int header_idx;
off_t header_offset;
int subtypes[16];
} ubi_sb_header;
static VGMSTREAM * init_vgmstream_ubi_sb_main(ubi_sb_header *sb, STREAMFILE *streamFile);
@ -91,8 +91,9 @@ static int config_sb_header_version(ubi_sb_header * sb, STREAMFILE *streamFile);
/* .SBx - banks from Ubisoft's sound engine ("DARE" / "UbiSound Driver") games in ~2000-2008 */
VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) {
STREAMFILE *streamTest = NULL;
int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL;
int16_t(*read_16bit)(off_t, STREAMFILE*) = NULL;
//int16_t(*read_16bit)(off_t, STREAMFILE*) = NULL;
ubi_sb_header sb = { 0 };
int ok;
int target_stream = streamFile->stream_index;
@ -152,10 +153,10 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) {
sb.platform == UBI_WII);
if (sb.big_endian) {
read_32bit = read_32bitBE;
read_16bit = read_16bitBE;
//read_16bit = read_16bitBE;
} else {
read_32bit = read_32bitLE;
read_16bit = read_16bitLE;
//read_16bit = read_16bitLE;
}
/* file layout is: base header, section1, section2, extra section, section3, data (all except base header can be null) */
@ -181,20 +182,27 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) {
sb.sounds_offset = sb.section3_offset + sb.section3_entry_size * sb.section3_num;
sb.is_map = 0;
/* use smaller I/O buffer for performance, as this read lots of small headers all over the place */
streamTest = reopen_streamfile(streamFile, 0x100);
if (!streamTest) goto fail;
/* main parse */
if (!parse_sb_header(&sb, streamFile, target_stream))
if (!parse_sb_header(&sb, streamTest, target_stream))
goto fail;
close_streamfile(streamTest);
return init_vgmstream_ubi_sb_main(&sb, streamFile);
fail:
close_streamfile(streamTest);
return NULL;
}
/* .SMx - essentially a set of SBx files, one per map, compiled into one file */
VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) {
STREAMFILE *streamTest = NULL;
int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL;
int16_t(*read_16bit)(off_t, STREAMFILE*) = NULL;
//int16_t(*read_16bit)(off_t, STREAMFILE*) = NULL;
ubi_sb_header sb = { 0 };
size_t map_entry_size;
int ok, i;
@ -251,10 +259,10 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) {
sb.platform == UBI_WII);
if (sb.big_endian) {
read_32bit = read_32bitBE;
read_16bit = read_16bitBE;
//read_16bit = read_16bitBE;
} else {
read_32bit = read_32bitLE;
read_16bit = read_16bitLE;
//read_16bit = read_16bitLE;
}
sb.is_map = 1;
@ -268,6 +276,10 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) {
goto fail;
}
/* use smaller I/O buffer for performance, as this read lots of small headers all over the place */
streamTest = reopen_streamfile(streamFile, 0x100);
if (!streamTest) goto fail;
map_entry_size = (sb.map_version < 2) ? 0x30 : 0x34;
for (i = 0; i < sb.map_num; i++) {
@ -309,7 +321,7 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) {
sb.extra_section_offset += sb.section4_offset;
}
if (!parse_sb_header(&sb, streamFile, target_stream))
if (!parse_sb_header(&sb, streamTest, target_stream))
goto fail;
}
@ -323,9 +335,11 @@ VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE *streamFile) {
goto fail;
}
close_streamfile(streamTest);
return init_vgmstream_ubi_sb_main(&sb, streamFile);
fail:
close_streamfile(streamTest);
return NULL;
}
@ -482,7 +496,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_main(ubi_sb_header *sb, STREAMFILE *str
uint8_t buf[0x100];
uint32_t sec1_num, sec2_num, sec3_num, num_frames, bits_per_frame;
uint8_t flag;
size_t bytes, frame_size, chunk_size, header_size, data_size;
size_t bytes, chunk_size, header_size, data_size;
off_t header_offset;
chunk_size = 0x20;
@ -581,6 +595,180 @@ fail:
}
/* debug stuff, for now */
static void parse_descriptor_subtype(ubi_sb_header * sb, uint32_t descriptor_subtype, off_t offset) {
/* type may be flags? */
/* all types may contain memory garbage, making it harder to identify */
switch(descriptor_subtype) {
/* standard audio */
case 0x01: sb->subtypes[0x01]++; break;
/* audio/garbage? [Splinter Cell: Chaos Theory] */
/* audio/garbage? [Surf's Up-map] */
/* audio/garbage? [TMNT-map] */
/* config? [Brothers in Arms: D-Day] */
/* config? [Surf's Up-bank] */
case 0x02: sb->subtypes[0x02]++; break;
/* audio/garbage? [Splinter Cell: Chaos Theory] */
/* audio/garbage? [TMNT-map] */
/* config? [Prince of Persia: The Two Thrones] */
case 0x03: sb->subtypes[0x03]++; break;
/* audio/garbage? [Splinter Cell: Chaos Theory] */
/* audio/garbage? [TMNT-map] */
/* config? [Rainbow Six 3] */
/* config? [Prince of Persia: Warrior Within] */
/* config? [Prince of Persia: The Two Thrones] */
/* config? [TMNT-bank] */
/* config? [Surf's Up-map] */
/* audio/garbage? [Myst IV demo] */
/* config? [Brothers in Arms: D-Day] */
/* config? [Surf's Up-bank] */
case 0x04: sb->subtypes[0x04]++; break;
/* audio/garbage? [Splinter Cell: Chaos Theory] */
/* audio/garbage? [TMNT-map] */
/* config? [Prince of Persia: Warrior Within] */
/* config? [Prince of Persia: The Two Thrones] */
/* config? [Prince of Persia: Revelations] */
/* config? [TMNT-bank] */
case 0x05: sb->subtypes[0x05]++; break;
/* layer? [Surf's Up-map] */
/* layer [TMNT-bank] */
/* layer [TMNT-map] */
case 0x06: sb->subtypes[0x06]++; break;
/* audio/garbage? [Splinter Cell: Chaos Theory] */
/* config? [Brothers in Arms: D-Day] */
/* config? [Surf's Up-bank] */
case 0x07: sb->subtypes[0x07]++; break;
/* audio/garbage? [Splinter Cell: Chaos Theory] */
/* audio/garbage? [TMNT-map] */
/* config? [Prince of Persia: Warrior Within] */
/* config? [Prince of Persia: The Two Thrones] */
/* config? [TMNT-bank] */
/* config? [Surf's Up-map] */
/* config? [Brothers in Arms: D-Day] */
/* config? [Surf's Up-bank] */
case 0x08: sb->subtypes[0x08]++; break;
/* related to voices? [Prince of Persia: Sands of Time] */
/* config? [Rainbow Six 3] */
/* config? [Myst IV demo] */
case 0x0a: sb->subtypes[0x0a]++; break;
/* config? [Prince of Persia: Sands of Time] */
/* config? [Rainbow Six 3] */
case 0x0c: sb->subtypes[0x0c]++; break;
/* layer [Prince of Persia: Sands of Time] */
/* layer [Rainbow Six 3] */
case 0x0d: sb->subtypes[0x0d]++; break;
/* config? [Rainbow Six 3] */
/* config? [Myst IV demo] */
case 0x0f: sb->subtypes[0x0f]++; break;
default:
VGM_LOG("UBI SB: unknown subtype %x at %x size %x\n", descriptor_subtype, (uint32_t)offset, sb->section2_entry_size);
break; //goto fail;
}
//;VGM_ASSERT(descriptor_subtype != 0x01, "UBI SB: subtype %x at %x size %x\n", descriptor_subtype, (uint32_t)offset, sb->section2_entry_size);
/* 0x06 layer [TMNT-bank] */
/* - subtype header:
* 0x1c: external flag?
* 0x20: layers/channels?
* 0x24: layers/channels?
* 0x28: config? same for all
* 0x2c: stream size
* 0x30: stream offset
* 0x38: always 0x037c
* (todo sample rate?)
*
* - layer header at stream_offset:
* 0x00: version? (0x08000B00)
* 0x04: 0x0e?
* 0x08: layers/channels?
* 0x0c: blocks count
* 0x10: block header size
* 0x14: size of header sizes + codec headers
* 0x18: block size
* - per layer:
* 0x00: layer header size
* - per layer
* 0x00~0x20: standard Ubi IMA header (version 0x05)
*
* - blocked data:
* 0x00: always 0x03
* 0x04: block size
* - per layer:
* 0x00: layer data size (varies between blocks, and one layer may have more than other)
*/
/* 0x06 layer [TMNT-map] */
/* - subtype header:
* 0x20: layers/channels?
* 0x24: layers/channels * 2?
* 0x28: config? same for all
* 0x2c: stream size
* 0x30: stream offset
* 0x38: 0x01D0/0118/etc
* 0x40: external flag?
* 0x48: codec?
* 0x54: codec?
* (todo sample rate?)
*
* - layer header at stream_offset:
* - blocked data:
* same as TMNT-bank, but codec header size is 0
*/
//todo Surf's Up-map
/* 0x0d layer [Prince of Persia: Sands of Time] */
/* 0x0d layer [Rainbow Six 3] */ //todo also check layer header
/* - subtype header (bizarrely but thankfully doesn't change between platforms):
* 0x1c: sample rate * layers
* 0x20: layers/channels?
* 0x2c: external flag?
* 0x30: external name
* 0x58: stream offset
* 0x5c: original rate * layers?
* 0x60: stream size (not including padding)
* 0x64: number of samples
*
* - layer header at stream_offset:
* 0x00: version? (0x04000000)
* 0x04: layers
* 0x08: stream size (not including padding)
* 0x0c: blocks count
* 0x10: block header size
* 0x14: block size
* 0x18: ?
* 0x1c: size of next section?
* - per layer:
* 0x00: layer header size
* codec header per layer
* 0x00~0x20: standard Ubi IMA header (version 0x05)
*
* - blocked data:
* 0x00: block number (from 0x01 to block_count)
* 0x04: current offset (within stream_offset)
* 0x08: always 0x03
* - per layer:
* 0x00: layer data size (varies between blocks, and one layer may have more than other)
*/
}
static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int target_stream) {
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
@ -599,11 +787,15 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int targe
/* find target stream info in section2 */
for (i = 0; i < sb->section2_num; i++) {
off_t offset = sb->section2_offset + sb->section2_entry_size*i;
uint32_t descriptor_subtype;
/* ignore non-audio entry (other types seem to have config data) */
if (read_32bit(offset + 0x04, streamFile) != 0x01)
descriptor_subtype = read_32bit(offset + 0x04, streamFile);
parse_descriptor_subtype(sb, descriptor_subtype, offset);
/* ignore non-audio entries */
if (descriptor_subtype != 0x01)
continue;
//;VGM_LOG("SB at %lx\n", offset);
//;VGM_LOG("UBI SB: type at %lx\n", offset);
/* weird case when there is no internal substream ID and just seem to rotate every time type changes, joy */
if (sb->has_rotating_ids) { /* assumes certain configs can't happen in this case */
@ -655,6 +847,7 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int targe
continue;
//;VGM_LOG("target at offset=%lx (size=%x)\n", offset, sb->section2_entry_size);
/* parse audio entry based on config */
sb->header_offset = offset;
sb->header_idx = i;
@ -719,6 +912,17 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int targe
}
}
#if 0
{
int i;
VGM_LOG("UBI subtypes: ");
for (i = 0; i < 16; i++) {
VGM_ASSERT(sb->subtypes[i], "%02x=%i ",i,sb->subtypes[i]);
}
VGM_LOG("\n");
}
#endif
if (sb->is_map) {
if (bank_streams == 0 || target_stream <= prev_streams || target_stream > sb->total_streams)
return 1; /* Target stream is not in this map */
@ -857,9 +1061,9 @@ static int parse_sb_header(ubi_sb_header * sb, STREAMFILE *streamFile, int targe
for (i = 0; i < sb->section3_num; i++) {
off_t offset = sb->section3_offset + 0x14 * i;
off_t table_offset = read_32bit(offset + 0x04, streamFile) + sb->section3_offset;
off_t table_num = read_32bit(offset + 0x08, streamFile);
uint32_t table_num = read_32bit(offset + 0x08, streamFile);
off_t table2_offset = read_32bit(offset + 0x0c, streamFile) + sb->section3_offset;
off_t table2_num = read_32bit(offset + 0x10, streamFile);
uint32_t table2_num = read_32bit(offset + 0x10, streamFile);
for (j = 0; j < table_num; j++) {
int idx = read_32bit(table_offset + 0x08 * j + 0x00, streamFile) & 0x0000FFFF;

View File

@ -2,7 +2,7 @@
#include "../layout/layout.h"
#include "../coding/coding.h"
/* CD-XA - from Sony PS1 CDs */
/* CD-XA - from Sony PS1 and Philips CD-i CD audio */
VGMSTREAM * init_vgmstream_xa(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset;
@ -10,6 +10,7 @@ VGMSTREAM * init_vgmstream_xa(STREAMFILE *streamFile) {
int is_blocked;
size_t file_size = get_streamfile_size(streamFile);
/* checks
* .xa: common, .str: sometimes (mainly videos)
* .adp: Phantasy Star Collection (SAT) raw XA */
@ -53,7 +54,7 @@ VGMSTREAM * init_vgmstream_xa(STREAMFILE *streamFile) {
for (i = 0; i < (sector_size/block_size); i++) {
/* XA headers checks: filter indexes should be 0..3, and shifts 0..C */
for (j = 0; j < 16; j++) {
uint8_t header = (uint8_t)read_8bit(test_offset + i, streamFile);
uint8_t header = (uint8_t)read_8bit(test_offset + j, streamFile);
if (((header >> 4) & 0xF) > 0x03)
goto fail;
if (((header >> 0) & 0xF) > 0x0c)

View File

@ -87,8 +87,9 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
/* checks */
/* .xwb: standard
* .xna: Touhou Makukasai ~ Fantasy Danmaku Festival (PC) */
if (!check_extensions(streamFile,"xwb,xna"))
* .xna: Touhou Makukasai ~ Fantasy Danmaku Festival (PC)
* (extensionless): Grabbed by the Ghoulies (Xbox) */
if (!check_extensions(streamFile,"xwb,xna,"))
goto fail;
if ((read_32bitBE(0x00,streamFile) != 0x57424E44) && /* "WBND" (LE) */
(read_32bitBE(0x00,streamFile) != 0x444E4257)) /* "DNBW" (BE) */

View File

@ -800,6 +800,15 @@ STREAMFILE * open_streamfile_by_filename(STREAMFILE *streamFile, const char * na
return streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
}
STREAMFILE * reopen_streamfile(STREAMFILE *streamFile, size_t buffer_size) {
char pathname[PATH_LIMIT];
if (buffer_size == 0)
buffer_size = STREAMFILE_DEFAULT_BUFFER_SIZE;
streamFile->get_name(streamFile,pathname,sizeof(pathname));
return streamFile->open(streamFile,pathname,buffer_size);
}
/* Read a line into dst. The source files are lines separated by CRLF (Windows) / LF (Unux) / CR (Mac).
* The line will be null-terminated and CR/LF removed if found.

View File

@ -123,6 +123,10 @@ STREAMFILE * open_streamfile_by_ext(STREAMFILE *streamFile, const char * ext);
* Can be used to get companion files. */
STREAMFILE * open_streamfile_by_filename(STREAMFILE *streamFile, const char * filename);
/* Reopen a STREAMFILE with a different buffer size, for fine-tuned bigfile parsing.
* Uses default buffer size when buffer_size is 0 */
STREAMFILE * reopen_streamfile(STREAMFILE *streamFile, size_t buffer_size);
/* close a file, destroy the STREAMFILE object */
static inline void close_streamfile(STREAMFILE * streamfile) {

View File

@ -84,6 +84,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_pos,
init_vgmstream_nwa,
init_vgmstream_ea_1snh,
init_vgmstream_ea_eacs,
init_vgmstream_xss,
init_vgmstream_sl3,
init_vgmstream_hgc1,

View File

@ -462,6 +462,7 @@ typedef enum {
meta_EA_SCHL_fixed, /* Electronic Arts SCHl with fixed header */
meta_EA_BNK, /* Electronic Arts BNK */
meta_EA_1SNH, /* Electronic Arts 1SNh/EACS */
meta_EA_EACS,
meta_RAW, /* RAW PCM file */