Add .isb MPEG/XMA [Mass Effect (multi)]

This commit is contained in:
bnnm 2020-04-05 22:07:51 +02:00
parent dc4556b0ec
commit 41fe7ad27b
3 changed files with 103 additions and 33 deletions

View File

@ -2,32 +2,38 @@
#include "../coding/coding.h" #include "../coding/coding.h"
/* .ISB - Creative ISACT (Interactive Spatial Audio Composition Tools) middleware [Psychonauts (PC)] */ /* .ISB - Creative ISACT (Interactive Spatial Audio Composition Tools) middleware [Psychonauts (PC), Mass Effect (multi)] */
VGMSTREAM * init_vgmstream_isb(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_isb(STREAMFILE *sf) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM * vgmstream = NULL;
off_t start_offset = 0, name_offset = 0; off_t start_offset = 0, name_offset = 0;
size_t stream_size = 0, name_size = 0; size_t stream_size = 0, name_size = 0;
int loop_flag = 0, channel_count = 0, sample_rate = 0, codec = 0, pcm_bytes = 0, bps = 0; int loop_flag = 0, channel_count = 0, sample_rate = 0, codec = 0, pcm_bytes = 0, bps = 0;
int total_subsongs, target_subsong = streamFile->stream_index; int total_subsongs, target_subsong = sf->stream_index;
uint32_t (*read_u32me)(off_t,STREAMFILE*);
uint32_t (*read_u32ce)(off_t,STREAMFILE*);
int big_endian;
/* checks */ /* checks */
if (!check_extensions(streamFile, "isb")) if (!check_extensions(sf, "isb"))
goto fail; goto fail;
if (read_32bitBE(0x00,streamFile) != 0x52494646) /* "RIFF" */ big_endian = read_u32be(0x00,sf) == 0x46464952; /* "FFIR" */
read_u32me = big_endian ? read_u32be : read_u32le; /* machine endianness... */
read_u32ce = big_endian ? read_u32le : read_u32be; /* chunks change with endianness but this just reads as BE */
if (read_u32ce(0x00,sf) != 0x52494646) /* "RIFF" */
goto fail; goto fail;
if (read_32bitLE(0x04,streamFile) + 0x08 != get_streamfile_size(streamFile)) if (read_u32me(0x04,sf) + 0x08 != get_streamfile_size(sf))
goto fail; goto fail;
if (read_32bitBE(0x08,streamFile) != 0x69736266) /* "isbf" */ if (read_u32ce(0x08,sf) != 0x69736266) /* "isbf" */
goto fail; goto fail;
/* some files have a companion .icb, seems to be a cue file pointing here */ /* some files have a companion .icb, seems to be a cue file pointing here */
/* format is RIFF with many custom chunks, apparently for their DAW-like editor with /* format is RIFF with many custom chunks, apparently for their DAW-like editor with
* complex functions, but most seem always included by default and unused, and games * complex functions, but most seem always included by default and unused, and games
* Psychonauts seems to use the format as a simple audio bank. Mass Effect (X360) * seems to use the format as a simple audio bank. Psychonauts Xbox/PS2 doesn't use ISACT. */
* apparently uses ISACT, while Psychonauts Xbox/PS2 don't. */
{ {
off_t offset, max_offset, header_offset = 0; off_t offset, max_offset, header_offset = 0;
@ -38,15 +44,15 @@ VGMSTREAM * init_vgmstream_isb(STREAMFILE *streamFile) {
/* parse base RIFF */ /* parse base RIFF */
offset = 0x0c; offset = 0x0c;
max_offset = get_streamfile_size(streamFile); max_offset = get_streamfile_size(sf);
while (offset < max_offset) { while (offset < max_offset) {
uint32_t chunk_type = read_u32be(offset + 0x00,streamFile); uint32_t chunk_type = read_u32ce(offset + 0x00,sf);
uint32_t chunk_size = read_s32le(offset + 0x04,streamFile); uint32_t chunk_size = read_u32me(offset + 0x04,sf);
offset += 0x08; offset += 0x08;
switch(chunk_type) { switch(chunk_type) {
case 0x4C495354: /* "LIST" */ case 0x4C495354: /* "LIST" */
if (read_u32be(offset, streamFile) != 0x73616D70) /* "samp" */ if (read_u32ce(offset, sf) != 0x73616D70) /* "samp" */
break; /* there are "bfob" LIST without data */ break; /* there are "bfob" LIST without data */
total_subsongs++; total_subsongs++;
@ -72,8 +78,8 @@ VGMSTREAM * init_vgmstream_isb(STREAMFILE *streamFile) {
offset = header_offset + 0x04; offset = header_offset + 0x04;
max_offset = offset + header_size; max_offset = offset + header_size;
while (offset < max_offset) { while (offset < max_offset) {
uint32_t chunk_type = read_u32be(offset + 0x00,streamFile); uint32_t chunk_type = read_u32ce(offset + 0x00,sf);
uint32_t chunk_size = read_s32le(offset + 0x04,streamFile); uint32_t chunk_size = read_u32me(offset + 0x04,sf);
offset += 0x08; offset += 0x08;
switch(chunk_type) { switch(chunk_type) {
@ -83,21 +89,21 @@ VGMSTREAM * init_vgmstream_isb(STREAMFILE *streamFile) {
break; break;
case 0x63686E6B: /* "chnk" */ case 0x63686E6B: /* "chnk" */
channel_count = read_u32le(offset + 0x00, streamFile); channel_count = read_u32me(offset + 0x00, sf);
break; break;
case 0x73696E66: /* "sinf" */ case 0x73696E66: /* "sinf" */
/* 0x00: null? */ /* 0x00: null? */
/* 0x04: some value? */ /* 0x04: some value? */
sample_rate = read_u32le(offset + 0x08, streamFile); sample_rate = read_u32me(offset + 0x08, sf);
pcm_bytes = read_u32le(offset + 0x0c, streamFile); pcm_bytes = read_u32me(offset + 0x0c, sf);
bps = read_u16le(offset + 0x10, streamFile); bps = read_u16le(offset + 0x10, sf);
/* 0x12: some value? */ /* 0x12: some value? */
break; break;
case 0x636D7069: /* "cmpi" */ case 0x636D7069: /* "cmpi" */
codec = read_u32le(offset + 0x00, streamFile); codec = read_u32me(offset + 0x00, sf);
if (read_u32le(offset + 0x04, streamFile) != codec) { if (read_u32me(offset + 0x04, sf) != codec) {
VGM_LOG("ISB: unknown compression repeat\n"); VGM_LOG("ISB: unknown compression repeat\n");
goto fail; goto fail;
} }
@ -134,11 +140,17 @@ VGMSTREAM * init_vgmstream_isb(STREAMFILE *streamFile) {
vgmstream = allocate_vgmstream(channel_count, loop_flag); vgmstream = allocate_vgmstream(channel_count, loop_flag);
if (!vgmstream) goto fail; if (!vgmstream) goto fail;
vgmstream->meta_type = meta_WAF; //todo vgmstream->meta_type = meta_ISB;
vgmstream->sample_rate = sample_rate; vgmstream->sample_rate = sample_rate;
vgmstream->num_streams = total_subsongs; vgmstream->num_streams = total_subsongs;
vgmstream->stream_size = stream_size; vgmstream->stream_size = stream_size;
if (name_offset) { /* UTF16 but only uses lower bytes */
if (name_size > STREAM_NAME_SIZE)
name_size = STREAM_NAME_SIZE;
read_string_utf16(vgmstream->stream_name,name_size, name_offset, sf, big_endian);
}
switch(codec) { switch(codec) {
case 0x00: case 0x00:
if (bps == 8) { if (bps == 8) {
@ -162,7 +174,7 @@ VGMSTREAM * init_vgmstream_isb(STREAMFILE *streamFile) {
#ifdef VGM_USE_VORBIS #ifdef VGM_USE_VORBIS
case 0x02: case 0x02:
vgmstream->codec_data = init_ogg_vorbis(streamFile, start_offset, stream_size, NULL); vgmstream->codec_data = init_ogg_vorbis(sf, start_offset, stream_size, NULL);
if (!vgmstream->codec_data) goto fail; if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_OGG_VORBIS; vgmstream->coding_type = coding_OGG_VORBIS;
vgmstream->layout_type = layout_none; vgmstream->layout_type = layout_none;
@ -170,18 +182,64 @@ VGMSTREAM * init_vgmstream_isb(STREAMFILE *streamFile) {
break; break;
#endif #endif
default: /* according to press releases ISACT may support WMA and XMA */ #ifdef VGM_USE_FFMPEG
case 0x04: {
uint8_t buf[0x100];
size_t bytes;
off_t fmt_offset = start_offset;
size_t fmt_size = 0x20;
start_offset += fmt_size;
stream_size -= fmt_size;
/* XMA1 "fmt" chunk (BE, unlike the usual LE) */
bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf,sizeof(buf), fmt_offset,fmt_size, stream_size, sf, 1);
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, start_offset, stream_size);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
vgmstream->num_samples = pcm_bytes / channel_count / (bps/8);
xma_fix_raw_samples(vgmstream, sf, start_offset, stream_size, fmt_offset, 1,1);
break;
}
#endif
case 0x05: {
//TODO: improve
VGMSTREAM *temp_vgmstream = NULL;
STREAMFILE *temp_sf = NULL;
temp_sf = setup_subfile_streamfile(sf, start_offset, stream_size, "msf");
if (!temp_sf) goto fail;
temp_vgmstream = init_vgmstream_msf(temp_sf);
if (temp_vgmstream) {
temp_vgmstream->num_streams = vgmstream->num_streams;
temp_vgmstream->stream_size = vgmstream->stream_size;
temp_vgmstream->meta_type = vgmstream->meta_type;
strcpy(temp_vgmstream->stream_name, vgmstream->stream_name);
close_streamfile(temp_sf);
close_vgmstream(vgmstream);
return temp_vgmstream;
}
else {
close_streamfile(temp_sf);
goto fail;
}
break;
}
default: /* according to press releases ISACT may support WMA */
VGM_LOG("ISB: unknown codec %i\n", codec); VGM_LOG("ISB: unknown codec %i\n", codec);
goto fail; goto fail;
} }
if (name_offset) { /* UTF16 but only uses lower bytes */
if (name_size > STREAM_NAME_SIZE)
name_size = STREAM_NAME_SIZE;
read_string_utf16le(vgmstream->stream_name,name_size, name_offset, streamFile);
}
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) if (!vgmstream_open_stream(vgmstream, sf, start_offset))
goto fail; goto fail;
return vgmstream; return vgmstream;

View File

@ -1007,11 +1007,14 @@ fail:
if (buf) buf[0] = '\0'; if (buf) buf[0] = '\0';
return 0; return 0;
} }
size_t read_string_utf16le(char *buf, size_t buf_size, off_t offset, STREAMFILE *sf) {
size_t read_string_utf16(char *buf, size_t buf_size, off_t offset, STREAMFILE *sf, int big_endian) {
size_t pos, offpos; size_t pos, offpos;
uint16_t (*read_u16)(off_t,STREAMFILE*) = big_endian ? read_u16be : read_u16le;
for (pos = 0, offpos = 0; pos < buf_size; pos++, offpos += 2) { for (pos = 0, offpos = 0; pos < buf_size; pos++, offpos += 2) {
char c = read_u16le(offset + offpos, sf) & 0xFF; /* lower byte for now */ char c = read_u16(offset + offpos, sf) & 0xFF; /* lower byte for now */
if (buf) buf[pos] = c; if (buf) buf[pos] = c;
if (c == '\0') if (c == '\0')
return pos; return pos;
@ -1028,6 +1031,13 @@ fail:
return 0; return 0;
} }
size_t read_string_utf16le(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf) {
return read_string_utf16(buf, buf_size, offset, sf, 0);
}
size_t read_string_utf16be(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf) {
return read_string_utf16(buf, buf_size, offset, sf, 1);
}
size_t read_key_file(uint8_t *buf, size_t buf_size, STREAMFILE *sf) { size_t read_key_file(uint8_t *buf, size_t buf_size, STREAMFILE *sf) {

View File

@ -336,7 +336,9 @@ size_t read_line(char *buf, int buf_size, off_t offset, STREAMFILE *sf, int *p_l
/* reads a c-string (ANSI only), up to bufsize or NULL, returning size. buf is optional (works as get_string_size). */ /* reads a c-string (ANSI only), up to bufsize or NULL, returning size. buf is optional (works as get_string_size). */
size_t read_string(char *buf, size_t buf_size, off_t offset, STREAMFILE *sf); size_t read_string(char *buf, size_t buf_size, off_t offset, STREAMFILE *sf);
/* reads a UTF16 string... but actually only as ANSI (discards the upper byte) */ /* reads a UTF16 string... but actually only as ANSI (discards the upper byte) */
size_t read_string_utf16le(char *buf, size_t buf_size, off_t offset, STREAMFILE *sf); size_t read_string_utf16(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf, int big_endian);
size_t read_string_utf16le(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf);
size_t read_string_utf16be(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf);
/* Opens a file containing decryption keys and copies to buffer. /* Opens a file containing decryption keys and copies to buffer.
* Tries "(name.ext)key" (per song), "(.ext)key" (per folder) keynames. * Tries "(name.ext)key" (per song), "(.ext)key" (per folder) keynames.