mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-12-03 02:27:18 +01:00
commit
ddb9eaf8f6
@ -845,7 +845,7 @@ JIN003.XAG: 0x180
|
||||
```
|
||||
|
||||
|
||||
**Grandia (PS1) bgm.txth**
|
||||
#### Grandia (PS1) bgm.txth
|
||||
```
|
||||
header_file = GM1.IDX
|
||||
body_file = GM1.STZ
|
||||
|
@ -348,6 +348,7 @@ uint32_t ffmpeg_get_channel_layout(ffmpeg_codec_data * data);
|
||||
void ffmpeg_set_channel_remapping(ffmpeg_codec_data * data, int *channels_remap);
|
||||
const char* ffmpeg_get_codec_name(ffmpeg_codec_data * data);
|
||||
void ffmpeg_set_force_seek(ffmpeg_codec_data * data);
|
||||
const char* ffmpeg_get_metadata_value(ffmpeg_codec_data* data, const char* key);
|
||||
|
||||
|
||||
/* ffmpeg_decoder_utils.c (helper-things) */
|
||||
|
@ -155,6 +155,8 @@ int ffmpeg_make_riff_xma1(uint8_t * buf, size_t buf_size, size_t sample_count, s
|
||||
put_16bitLE(buf+off+0x12, speakers);
|
||||
}
|
||||
|
||||
/* xmaencode decoding rejects XMA1 without "seek" chunk, though it doesn't seem to use it */
|
||||
|
||||
memcpy(buf+riff_size-4-4, "data", 4);
|
||||
put_32bitLE(buf+riff_size-4, data_size); /* data size */
|
||||
|
||||
|
@ -941,4 +941,22 @@ void ffmpeg_set_force_seek(ffmpeg_codec_data * data) {
|
||||
//stream = data->formatCtx->streams[data->streamIndex];
|
||||
}
|
||||
|
||||
const char* ffmpeg_get_metadata_value(ffmpeg_codec_data* data, const char* key) {
|
||||
AVDictionary* avd;
|
||||
AVDictionaryEntry* avde;
|
||||
|
||||
if (!data || !data->codec)
|
||||
return NULL;
|
||||
|
||||
avd = data->formatCtx->streams[data->streamIndex]->metadata;
|
||||
if (!avd)
|
||||
return NULL;
|
||||
|
||||
avde = av_dict_get(avd, key, NULL, AV_DICT_IGNORE_SUFFIX);
|
||||
if (!avde)
|
||||
return NULL;
|
||||
|
||||
return avde->value;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -276,6 +276,10 @@
|
||||
RelativePath=".\meta\ea_schl_streamfile.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\fsb_encrypted_streamfile.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\fsb_interleave_streamfile.h"
|
||||
>
|
||||
|
@ -111,6 +111,7 @@
|
||||
<ClInclude Include="meta\ea_eaac_streamfile.h" />
|
||||
<ClInclude Include="meta\ea_eaac_opus_streamfile.h" />
|
||||
<ClInclude Include="meta\ea_schl_streamfile.h" />
|
||||
<ClInclude Include="meta\fsb_encrypted_streamfile.h" />
|
||||
<ClInclude Include="meta\fsb_interleave_streamfile.h" />
|
||||
<ClInclude Include="meta\fsb5_streamfile.h" />
|
||||
<ClInclude Include="meta\jstm_streamfile.h" />
|
||||
|
@ -104,6 +104,9 @@
|
||||
<ClInclude Include="meta\ea_schl_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta\fsb_encrypted_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta\fsb_interleave_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
@ -3,34 +3,28 @@
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
|
||||
static int read_pos_file(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile);
|
||||
static int read_pos_file(uint8_t* buf, size_t bufsize, STREAMFILE* sf);
|
||||
static int find_ogg_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end);
|
||||
|
||||
/**
|
||||
* Generic init FFmpeg and vgmstream for any file supported by FFmpeg.
|
||||
* Called by vgmstream when trying to identify the file type (if the player allows it).
|
||||
*/
|
||||
VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile) {
|
||||
return init_vgmstream_ffmpeg_offset( streamFile, 0, streamFile->get_size(streamFile) );
|
||||
}
|
||||
|
||||
VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
ffmpeg_codec_data *data = NULL;
|
||||
/* parses any file supported by FFmpeg and not handled elsewhere (mainly: MP4/AAC, MP3, MPC, FLAC) */
|
||||
VGMSTREAM* init_vgmstream_ffmpeg(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
ffmpeg_codec_data* data = NULL;
|
||||
int loop_flag = 0;
|
||||
int32_t loop_start = -1, loop_end = -1, num_samples = 0;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
int32_t loop_start = 0, loop_end = 0, num_samples = 0;
|
||||
int total_subsongs, target_subsong = sf->stream_index;
|
||||
|
||||
/* no checks */
|
||||
//if (!check_extensions(streamFile, "..."))
|
||||
//if (!check_extensions(sf, "..."))
|
||||
// goto fail;
|
||||
|
||||
/* don't try to open headers and other mini files */
|
||||
if (get_streamfile_size(streamFile) <= 0x1000)
|
||||
if (get_streamfile_size(sf) <= 0x1000)
|
||||
goto fail;
|
||||
|
||||
|
||||
/* init ffmpeg */
|
||||
data = init_ffmpeg_offset(streamFile, start, size);
|
||||
data = init_ffmpeg_offset(sf, 0, get_streamfile_size(sf));
|
||||
if (!data) return NULL;
|
||||
|
||||
total_subsongs = data->streamCount;
|
||||
@ -41,80 +35,43 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start,
|
||||
{
|
||||
uint8_t posbuf[4+4+4];
|
||||
|
||||
if ( read_pos_file(posbuf, 4+4+4, streamFile) ) {
|
||||
loop_start = get_32bitLE(posbuf+0);
|
||||
loop_end = get_32bitLE(posbuf+4);
|
||||
if (read_pos_file(posbuf, 4+4+4, sf)) {
|
||||
loop_start = get_s32le(posbuf+0);
|
||||
loop_end = get_s32le(posbuf+4);
|
||||
loop_flag = 1; /* incorrect looping will be validated outside */
|
||||
/* FFmpeg can't always determine totalSamples correctly so optionally load it (can be 0/NULL)
|
||||
* won't crash and will output silence if no loop points and bigger than actual stream's samples */
|
||||
num_samples = get_32bitLE(posbuf+8);
|
||||
} else {
|
||||
char* endptr;
|
||||
AVDictionary *streamMetadata = data->formatCtx->streams[streamFile->stream_index]->metadata;
|
||||
|
||||
// Try to detect the loop flags based on current file metadata
|
||||
AVDictionaryEntry *avLoopStart = av_dict_get(streamMetadata, "LoopStart", NULL, AV_DICT_IGNORE_SUFFIX);
|
||||
if (avLoopStart != NULL) {
|
||||
loop_start = strtol(avLoopStart->value, &endptr, 10);
|
||||
loop_flag = 1;
|
||||
}
|
||||
|
||||
AVDictionaryEntry *avLoopEnd = av_dict_get(streamMetadata, "LoopEnd", NULL, AV_DICT_IGNORE_SUFFIX);
|
||||
if (avLoopEnd != NULL) {
|
||||
loop_end = strtol(avLoopEnd->value, &endptr, 10);
|
||||
loop_flag = 1;
|
||||
}
|
||||
|
||||
if (loop_flag) {
|
||||
if (loop_end <= 0) {
|
||||
// Detected a loop, but loop_end is still undefined or wrong. Try to calculate it.
|
||||
AVDictionaryEntry *avLoopLength = av_dict_get(streamMetadata, "LoopLength", NULL, AV_DICT_IGNORE_SUFFIX);
|
||||
if (avLoopLength != NULL) {
|
||||
int loop_length = strtol(avLoopLength->value, &endptr, 10);
|
||||
|
||||
if (loop_start != -1) loop_end = loop_start + loop_length;
|
||||
}
|
||||
}
|
||||
|
||||
if (loop_end <= 0) {
|
||||
// Looks a calculation was not possible, or tag value is wrongly set. Use the end of track as end value
|
||||
loop_end = data->totalSamples;
|
||||
}
|
||||
|
||||
if (loop_start <= 0) {
|
||||
// Weird edge case: loopend is defined and there's a loop, but loopstart was never defined. Reset to sane value
|
||||
loop_start = 0;
|
||||
}
|
||||
} else {
|
||||
// Every other attempt to detect loop information failed, reset start/end flags to sane values
|
||||
loop_start = 0;
|
||||
loop_end = 0;
|
||||
}
|
||||
num_samples = get_s32le(posbuf+8);
|
||||
}
|
||||
}
|
||||
|
||||
/* try to read Ogg loop tags (abridged) */
|
||||
if (loop_flag == 0 && read_u32be(0x00, sf) == 0x4F676753) { /* "OggS" */
|
||||
loop_flag = find_ogg_loops(data, &loop_start, &loop_end);
|
||||
}
|
||||
|
||||
/* hack for AAC files (will return 0 samples if not an actual file) */
|
||||
if (!num_samples && check_extensions(streamFile, "aac,laac")) {
|
||||
num_samples = aac_get_samples(streamFile, 0x00, get_streamfile_size(streamFile));
|
||||
if (!num_samples && check_extensions(sf, "aac,laac")) {
|
||||
num_samples = aac_get_samples(sf, 0x00, get_streamfile_size(sf));
|
||||
}
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
/* hack for MP3 files (will return 0 samples if not an actual file)
|
||||
* .mus: Marc Ecko's Getting Up (PC) */
|
||||
if (!num_samples && check_extensions(streamFile, "mp3,lmp3,mus")) {
|
||||
num_samples = mpeg_get_samples(streamFile, 0x00, get_streamfile_size(streamFile));
|
||||
if (!num_samples && check_extensions(sf, "mp3,lmp3,mus")) {
|
||||
num_samples = mpeg_get_samples(sf, 0x00, get_streamfile_size(sf));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* hack for MPC, that seeks/resets incorrectly due to seek table shenanigans */
|
||||
if (read_32bitBE(0x00, streamFile) == 0x4D502B07 || /* "MP+\7" (Musepack V7) */
|
||||
read_32bitBE(0x00, streamFile) == 0x4D50434B) { /* "MPCK" (Musepack V8) */
|
||||
if (read_u32be(0x00, sf) == 0x4D502B07 || /* "MP+\7" (Musepack V7) */
|
||||
read_u32be(0x00, sf) == 0x4D50434B) { /* "MPCK" (Musepack V8) */
|
||||
ffmpeg_set_force_seek(data);
|
||||
}
|
||||
|
||||
/* default but often inaccurate when calculated using bitrate (wrong for VBR) */
|
||||
if (!num_samples) {
|
||||
num_samples = data->totalSamples;
|
||||
num_samples = data->totalSamples; /* may be 0 if FFmpeg can't precalculate it */
|
||||
}
|
||||
|
||||
|
||||
@ -129,15 +86,8 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start,
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
vgmstream->num_samples = num_samples;
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
}
|
||||
|
||||
/* this may happen for some streams if FFmpeg can't determine it (ex. AAC) */
|
||||
if (vgmstream->num_samples <= 0)
|
||||
goto fail;
|
||||
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data);
|
||||
|
||||
return vgmstream;
|
||||
@ -153,46 +103,94 @@ fail:
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* open file containing looping data and copy to buffer
|
||||
*
|
||||
* returns true if found and copied
|
||||
*/
|
||||
int read_pos_file(uint8_t * buf, size_t bufsize, STREAMFILE *streamFile) {
|
||||
/* open file containing looping data and copy to buffer, returns true if found and copied */
|
||||
int read_pos_file(uint8_t* buf, size_t bufsize, STREAMFILE* sf) {
|
||||
char posname[PATH_LIMIT];
|
||||
char filename[PATH_LIMIT];
|
||||
/*size_t bytes_read;*/
|
||||
STREAMFILE * streamFilePos= NULL;
|
||||
STREAMFILE* sf_pos = NULL;
|
||||
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
get_streamfile_name(sf,filename,sizeof(filename));
|
||||
|
||||
if (strlen(filename)+4 > sizeof(posname)) goto fail;
|
||||
if (strlen(filename)+4 > sizeof(posname))
|
||||
goto fail;
|
||||
|
||||
/* try to open a posfile using variations: "(name.ext).pos" */
|
||||
{
|
||||
strcpy(posname, filename);
|
||||
strcat(posname, ".pos");
|
||||
streamFilePos = streamFile->open(streamFile,posname,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (streamFilePos) goto found;
|
||||
sf_pos = open_streamfile(sf, posname);;
|
||||
if (sf_pos) goto found;
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
found:
|
||||
//if (get_streamfile_size(streamFilePos) != bufsize) goto fail;
|
||||
//if (get_streamfile_size(sf_pos) != bufsize) goto fail;
|
||||
|
||||
/* allow pos files to be of different sizes in case of new features, just fill all we can */
|
||||
memset(buf, 0, bufsize);
|
||||
read_streamfile(buf, 0, bufsize, streamFilePos);
|
||||
read_streamfile(buf, 0, bufsize, sf_pos);
|
||||
|
||||
close_streamfile(streamFilePos);
|
||||
close_streamfile(sf_pos);
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
if (streamFilePos) close_streamfile(streamFilePos);
|
||||
|
||||
close_streamfile(sf_pos);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* loop tag handling could be unified with ogg_vorbis.c, but that one has a extra features too */
|
||||
static int find_ogg_loops(ffmpeg_codec_data* data, int32_t* p_loop_start, int32_t* p_loop_end) {
|
||||
char* endptr;
|
||||
const char* value;
|
||||
int loop_flag = 0;
|
||||
int32_t loop_start = -1, loop_end = -1;
|
||||
|
||||
// Try to detect the loop flags based on current file metadata
|
||||
value = ffmpeg_get_metadata_value(data, "LoopStart");
|
||||
if (value != NULL) {
|
||||
loop_start = strtol(value, &endptr, 10);
|
||||
loop_flag = 1;
|
||||
}
|
||||
|
||||
value = ffmpeg_get_metadata_value(data, "LoopEnd");
|
||||
if (value != NULL) {
|
||||
loop_end = strtol(value, &endptr, 10);
|
||||
loop_flag = 1;
|
||||
}
|
||||
|
||||
if (loop_flag) {
|
||||
if (loop_end <= 0) {
|
||||
// Detected a loop, but loop_end is still undefined or wrong. Try to calculate it.
|
||||
value = ffmpeg_get_metadata_value(data, "LoopLength");
|
||||
if (value != NULL) {
|
||||
int loop_length = strtol(value, &endptr, 10);
|
||||
|
||||
if (loop_start != -1) loop_end = loop_start + loop_length;
|
||||
}
|
||||
}
|
||||
|
||||
if (loop_end <= 0) {
|
||||
// Looks a calculation was not possible, or tag value is wrongly set. Use the end of track as end value
|
||||
loop_end = data->totalSamples;
|
||||
}
|
||||
|
||||
if (loop_start <= 0) {
|
||||
// Weird edge case: loopend is defined and there's a loop, but loopstart was never defined. Reset to sane value
|
||||
loop_start = 0;
|
||||
}
|
||||
} else {
|
||||
// Every other attempt to detect loop information failed, reset start/end flags to sane values
|
||||
loop_start = 0;
|
||||
loop_end = 0;
|
||||
}
|
||||
|
||||
*p_loop_start = loop_start;
|
||||
*p_loop_end = loop_end;
|
||||
return loop_flag;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -67,7 +67,7 @@
|
||||
|
||||
|
||||
/* simplified struct based on the original definitions */
|
||||
typedef enum { MPEG, IMA, PSX, XMA2, DSP, CELT, PCM8, PCM16 } fsb_codec_t;
|
||||
typedef enum { MPEG, IMA, PSX, XMA, DSP, CELT, PCM8, PCM16 } fsb_codec_t;
|
||||
typedef struct {
|
||||
/* main header */
|
||||
uint32_t id;
|
||||
@ -269,7 +269,7 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
|
||||
if (fsb.mode & FSOUND_MPEG) fsb.codec = MPEG;
|
||||
else if (fsb.mode & FSOUND_IMAADPCM) fsb.codec = IMA;
|
||||
else if (fsb.mode & FSOUND_VAG) fsb.codec = PSX;
|
||||
else if (fsb.mode & FSOUND_XMA) fsb.codec = XMA2;
|
||||
else if (fsb.mode & FSOUND_XMA) fsb.codec = XMA;
|
||||
else if (fsb.mode & FSOUND_GCADPCM) fsb.codec = DSP;
|
||||
else if (fsb.mode & FSOUND_CELT) fsb.codec = CELT;
|
||||
else if (fsb.mode & FSOUND_8BITS) fsb.codec = PCM8;
|
||||
@ -382,14 +382,19 @@ VGMSTREAM* init_vgmstream_fsb(STREAMFILE* sf) {
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case XMA2: { /* FSB3: The Bourne Conspiracy 2008 (X360), FSB4: Armored Core V (X360), Hard Corps (X360) */
|
||||
case XMA: { /* FSB3: The Bourne Conspiracy 2008 (X360), FSB4: Armored Core V (X360), Hard Corps (X360) */
|
||||
uint8_t buf[0x100];
|
||||
size_t bytes, block_size, block_count;
|
||||
|
||||
block_size = 0x8000; /* FSB default */
|
||||
block_count = fsb.stream_size / block_size; /* not accurate but not needed (custom_data_offset+0x14 -1?) */
|
||||
if (fsb.version != FMOD_FSB_VERSION_4_0) { /* 3.x, though no actual output changes [ex. Guitar Hero III (X360)] */
|
||||
bytes = ffmpeg_make_riff_xma1(buf, sizeof(buf), fsb.num_samples, fsb.stream_size, fsb.channels, fsb.sample_rate, 0);
|
||||
}
|
||||
else {
|
||||
block_size = 0x8000; /* FSB default */
|
||||
block_count = fsb.stream_size / block_size; /* not accurate but not needed (custom_data_offset+0x14 -1?) */
|
||||
|
||||
bytes = ffmpeg_make_riff_xma2(buf,0x100, fsb.num_samples, fsb.stream_size, fsb.channels, fsb.sample_rate, block_count, block_size);
|
||||
bytes = ffmpeg_make_riff_xma2(buf, sizeof(buf), fsb.num_samples, fsb.stream_size, fsb.channels, fsb.sample_rate, block_count, block_size);
|
||||
}
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(sf, buf,bytes, fsb.stream_offset,fsb.stream_size);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
|
@ -1,164 +1,85 @@
|
||||
#include "meta.h"
|
||||
#include "fsb_keys.h"
|
||||
|
||||
#define FSB_KEY_MAX 128 /* probably 32 */
|
||||
|
||||
static STREAMFILE* setup_fsb_streamfile(STREAMFILE *streamFile, const uint8_t * key, size_t key_size, int is_alt);
|
||||
|
||||
|
||||
/* fully encrypted FSBs */
|
||||
VGMSTREAM * init_vgmstream_fsb_encrypted(STREAMFILE * streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
|
||||
/* checks */
|
||||
/* .fsb: standard
|
||||
* .fsb.xen: various Guitar Hero (X360) */
|
||||
if ( !check_extensions(streamFile, "fsb,xen") )
|
||||
goto fail;
|
||||
|
||||
/* ignore non-encrypted FSB */
|
||||
if ((read_32bitBE(0x00,streamFile) & 0xFFFFFF00) == 0x46534200) /* "FSB\0" */
|
||||
goto fail;
|
||||
|
||||
|
||||
/* try fsbkey + all combinations of FSB4/5 and decryption algorithms */
|
||||
{
|
||||
STREAMFILE *temp_streamFile = NULL;
|
||||
uint8_t key[FSB_KEY_MAX];
|
||||
size_t key_size = read_key_file(key, FSB_KEY_MAX, streamFile);
|
||||
|
||||
if (key_size) {
|
||||
{
|
||||
temp_streamFile = setup_fsb_streamfile(streamFile, key,key_size, 0);
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
if (!vgmstream) vgmstream = init_vgmstream_fsb(temp_streamFile);
|
||||
if (!vgmstream) vgmstream = init_vgmstream_fsb5(temp_streamFile);
|
||||
|
||||
close_streamfile(temp_streamFile);
|
||||
}
|
||||
|
||||
if (!vgmstream) {
|
||||
temp_streamFile = setup_fsb_streamfile(streamFile, key,key_size, 1);
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
if (!vgmstream) vgmstream = init_vgmstream_fsb(temp_streamFile);
|
||||
if (!vgmstream) vgmstream = init_vgmstream_fsb5(temp_streamFile);
|
||||
|
||||
close_streamfile(temp_streamFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* try all keys until one works */
|
||||
if (!vgmstream) {
|
||||
int i;
|
||||
STREAMFILE *temp_streamFile = NULL;
|
||||
|
||||
for (i = 0; i < fsbkey_list_count; i++) {
|
||||
fsbkey_info entry = fsbkey_list[i];
|
||||
//;VGM_LOG("fsbkey: size=%i, is_fsb5=%i, is_alt=%i\n", entry.fsbkey_size,entry.is_fsb5, entry.is_alt);
|
||||
|
||||
temp_streamFile = setup_fsb_streamfile(streamFile, entry.fsbkey, entry.fsbkey_size, entry.is_alt);
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
if (fsbkey_list[i].is_fsb5) {
|
||||
vgmstream = init_vgmstream_fsb5(temp_streamFile);
|
||||
} else {
|
||||
vgmstream = init_vgmstream_fsb(temp_streamFile);
|
||||
}
|
||||
|
||||
close_streamfile(temp_streamFile);
|
||||
if (vgmstream) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vgmstream)
|
||||
goto fail;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t key[FSB_KEY_MAX];
|
||||
size_t key_size;
|
||||
int is_alt;
|
||||
} fsb_decryption_data;
|
||||
|
||||
/* Encrypted FSB info from guessfsb and fsbext */
|
||||
static size_t fsb_decryption_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, fsb_decryption_data* data) {
|
||||
static const unsigned char reverse_bits_table[] = { /* LUT to simplify, could use some bitswap function */
|
||||
0x00,0x80,0x40,0xC0,0x20,0xA0,0x60,0xE0,0x10,0x90,0x50,0xD0,0x30,0xB0,0x70,0xF0,
|
||||
0x08,0x88,0x48,0xC8,0x28,0xA8,0x68,0xE8,0x18,0x98,0x58,0xD8,0x38,0xB8,0x78,0xF8,
|
||||
0x04,0x84,0x44,0xC4,0x24,0xA4,0x64,0xE4,0x14,0x94,0x54,0xD4,0x34,0xB4,0x74,0xF4,
|
||||
0x0C,0x8C,0x4C,0xCC,0x2C,0xAC,0x6C,0xEC,0x1C,0x9C,0x5C,0xDC,0x3C,0xBC,0x7C,0xFC,
|
||||
0x02,0x82,0x42,0xC2,0x22,0xA2,0x62,0xE2,0x12,0x92,0x52,0xD2,0x32,0xB2,0x72,0xF2,
|
||||
0x0A,0x8A,0x4A,0xCA,0x2A,0xAA,0x6A,0xEA,0x1A,0x9A,0x5A,0xDA,0x3A,0xBA,0x7A,0xFA,
|
||||
0x06,0x86,0x46,0xC6,0x26,0xA6,0x66,0xE6,0x16,0x96,0x56,0xD6,0x36,0xB6,0x76,0xF6,
|
||||
0x0E,0x8E,0x4E,0xCE,0x2E,0xAE,0x6E,0xEE,0x1E,0x9E,0x5E,0xDE,0x3E,0xBE,0x7E,0xFE,
|
||||
0x01,0x81,0x41,0xC1,0x21,0xA1,0x61,0xE1,0x11,0x91,0x51,0xD1,0x31,0xB1,0x71,0xF1,
|
||||
0x09,0x89,0x49,0xC9,0x29,0xA9,0x69,0xE9,0x19,0x99,0x59,0xD9,0x39,0xB9,0x79,0xF9,
|
||||
0x05,0x85,0x45,0xC5,0x25,0xA5,0x65,0xE5,0x15,0x95,0x55,0xD5,0x35,0xB5,0x75,0xF5,
|
||||
0x0D,0x8D,0x4D,0xCD,0x2D,0xAD,0x6D,0xED,0x1D,0x9D,0x5D,0xDD,0x3D,0xBD,0x7D,0xFD,
|
||||
0x03,0x83,0x43,0xC3,0x23,0xA3,0x63,0xE3,0x13,0x93,0x53,0xD3,0x33,0xB3,0x73,0xF3,
|
||||
0x0B,0x8B,0x4B,0xCB,0x2B,0xAB,0x6B,0xEB,0x1B,0x9B,0x5B,0xDB,0x3B,0xBB,0x7B,0xFB,
|
||||
0x07,0x87,0x47,0xC7,0x27,0xA7,0x67,0xE7,0x17,0x97,0x57,0xD7,0x37,0xB7,0x77,0xF7,
|
||||
0x0F,0x8F,0x4F,0xCF,0x2F,0xAF,0x6F,0xEF,0x1F,0x9F,0x5F,0xDF,0x3F,0xBF,0x7F,0xFF
|
||||
};
|
||||
size_t bytes_read;
|
||||
int i;
|
||||
|
||||
bytes_read = streamfile->read(streamfile, dest, offset, length);
|
||||
|
||||
/* decrypt data (inverted bits and xor) */
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
uint8_t xor = data->key[(offset + i) % data->key_size];
|
||||
uint8_t val = dest[i];
|
||||
if (data->is_alt) {
|
||||
dest[i] = reverse_bits_table[val ^ xor];
|
||||
}
|
||||
else {
|
||||
dest[i] = reverse_bits_table[val] ^ xor;
|
||||
}
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
static STREAMFILE* setup_fsb_streamfile(STREAMFILE *streamFile, const uint8_t * key, size_t key_size, int is_alt) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
fsb_decryption_data io_data = {0};
|
||||
size_t io_data_size = sizeof(fsb_decryption_data);
|
||||
|
||||
/* setup decryption with key (external) */
|
||||
if (!key_size || key_size > FSB_KEY_MAX) goto fail;
|
||||
|
||||
memcpy(io_data.key, key, key_size);
|
||||
io_data.key_size = key_size;
|
||||
io_data.is_alt = is_alt;
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, fsb_decryption_read,NULL);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,"fsb");
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "fsb_keys.h"
|
||||
#include "fsb_encrypted_streamfile.h"
|
||||
|
||||
|
||||
/* fully encrypted FSBs */
|
||||
VGMSTREAM* init_vgmstream_fsb_encrypted(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
|
||||
/* checks */
|
||||
/* .fsb: standard
|
||||
* .fsb.xen: various Guitar Hero (X360/PC) */
|
||||
if (!check_extensions(sf, "fsb,xen"))
|
||||
goto fail;
|
||||
|
||||
/* ignore non-encrypted FSB */
|
||||
if ((read_u32be(0x00,sf) & 0xFFFFFF00) == 0x46534200) /* "FSB\0" */
|
||||
goto fail;
|
||||
|
||||
|
||||
/* try fsbkey + all combinations of FSB4/5 and decryption algorithms */
|
||||
{
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
uint8_t key[FSB_KEY_MAX];
|
||||
size_t key_size = read_key_file(key, FSB_KEY_MAX, sf);
|
||||
|
||||
if (key_size) {
|
||||
{
|
||||
temp_sf = setup_fsb_streamfile(sf, key,key_size, 0);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
if (!vgmstream) vgmstream = init_vgmstream_fsb(temp_sf);
|
||||
if (!vgmstream) vgmstream = init_vgmstream_fsb5(temp_sf);
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
}
|
||||
|
||||
if (!vgmstream) {
|
||||
temp_sf = setup_fsb_streamfile(sf, key,key_size, 1);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
if (!vgmstream) vgmstream = init_vgmstream_fsb(temp_sf);
|
||||
if (!vgmstream) vgmstream = init_vgmstream_fsb5(temp_sf);
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* try all keys until one works */
|
||||
if (!vgmstream) {
|
||||
int i;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
|
||||
for (i = 0; i < fsbkey_list_count; i++) {
|
||||
fsbkey_info entry = fsbkey_list[i];
|
||||
//;VGM_LOG("fsbkey: size=%i, is_fsb5=%i, is_alt=%i\n", entry.fsbkey_size,entry.is_fsb5, entry.is_alt);
|
||||
|
||||
temp_sf = setup_fsb_streamfile(sf, entry.fsbkey, entry.fsbkey_size, entry.is_alt);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
if (fsbkey_list[i].is_fsb5) {
|
||||
vgmstream = init_vgmstream_fsb5(temp_sf);
|
||||
} else {
|
||||
vgmstream = init_vgmstream_fsb(temp_sf);
|
||||
}
|
||||
|
||||
if (vgmstream)
|
||||
dump_streamfile(temp_sf, 0);
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
if (vgmstream) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vgmstream)
|
||||
goto fail;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
73
src/meta/fsb_encrypted_streamfile.h
Normal file
73
src/meta/fsb_encrypted_streamfile.h
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef _FSB_ENCRYPTED_STREAMFILE_H_
|
||||
#define _FSB_ENCRYPTED_H_
|
||||
|
||||
#define FSB_KEY_MAX 128 /* probably 32 */
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t key[FSB_KEY_MAX];
|
||||
size_t key_size;
|
||||
int is_alt;
|
||||
} fsb_decryption_data;
|
||||
|
||||
/* Encrypted FSB info from guessfsb and fsbext */
|
||||
static size_t fsb_decryption_read(STREAMFILE* sf, uint8_t *dest, off_t offset, size_t length, fsb_decryption_data* data) {
|
||||
static const unsigned char reverse_bits_table[] = { /* LUT to simplify, could use some bitswap function */
|
||||
0x00,0x80,0x40,0xC0,0x20,0xA0,0x60,0xE0,0x10,0x90,0x50,0xD0,0x30,0xB0,0x70,0xF0,
|
||||
0x08,0x88,0x48,0xC8,0x28,0xA8,0x68,0xE8,0x18,0x98,0x58,0xD8,0x38,0xB8,0x78,0xF8,
|
||||
0x04,0x84,0x44,0xC4,0x24,0xA4,0x64,0xE4,0x14,0x94,0x54,0xD4,0x34,0xB4,0x74,0xF4,
|
||||
0x0C,0x8C,0x4C,0xCC,0x2C,0xAC,0x6C,0xEC,0x1C,0x9C,0x5C,0xDC,0x3C,0xBC,0x7C,0xFC,
|
||||
0x02,0x82,0x42,0xC2,0x22,0xA2,0x62,0xE2,0x12,0x92,0x52,0xD2,0x32,0xB2,0x72,0xF2,
|
||||
0x0A,0x8A,0x4A,0xCA,0x2A,0xAA,0x6A,0xEA,0x1A,0x9A,0x5A,0xDA,0x3A,0xBA,0x7A,0xFA,
|
||||
0x06,0x86,0x46,0xC6,0x26,0xA6,0x66,0xE6,0x16,0x96,0x56,0xD6,0x36,0xB6,0x76,0xF6,
|
||||
0x0E,0x8E,0x4E,0xCE,0x2E,0xAE,0x6E,0xEE,0x1E,0x9E,0x5E,0xDE,0x3E,0xBE,0x7E,0xFE,
|
||||
0x01,0x81,0x41,0xC1,0x21,0xA1,0x61,0xE1,0x11,0x91,0x51,0xD1,0x31,0xB1,0x71,0xF1,
|
||||
0x09,0x89,0x49,0xC9,0x29,0xA9,0x69,0xE9,0x19,0x99,0x59,0xD9,0x39,0xB9,0x79,0xF9,
|
||||
0x05,0x85,0x45,0xC5,0x25,0xA5,0x65,0xE5,0x15,0x95,0x55,0xD5,0x35,0xB5,0x75,0xF5,
|
||||
0x0D,0x8D,0x4D,0xCD,0x2D,0xAD,0x6D,0xED,0x1D,0x9D,0x5D,0xDD,0x3D,0xBD,0x7D,0xFD,
|
||||
0x03,0x83,0x43,0xC3,0x23,0xA3,0x63,0xE3,0x13,0x93,0x53,0xD3,0x33,0xB3,0x73,0xF3,
|
||||
0x0B,0x8B,0x4B,0xCB,0x2B,0xAB,0x6B,0xEB,0x1B,0x9B,0x5B,0xDB,0x3B,0xBB,0x7B,0xFB,
|
||||
0x07,0x87,0x47,0xC7,0x27,0xA7,0x67,0xE7,0x17,0x97,0x57,0xD7,0x37,0xB7,0x77,0xF7,
|
||||
0x0F,0x8F,0x4F,0xCF,0x2F,0xAF,0x6F,0xEF,0x1F,0x9F,0x5F,0xDF,0x3F,0xBF,0x7F,0xFF
|
||||
};
|
||||
size_t bytes_read;
|
||||
int i;
|
||||
|
||||
bytes_read = read_streamfile(dest, offset, length, sf);
|
||||
|
||||
/* decrypt data (inverted bits and xor) */
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
uint8_t xor = data->key[(offset + i) % data->key_size];
|
||||
uint8_t val = dest[i];
|
||||
if (data->is_alt) {
|
||||
dest[i] = reverse_bits_table[val ^ xor];
|
||||
}
|
||||
else {
|
||||
dest[i] = reverse_bits_table[val] ^ xor;
|
||||
}
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
static STREAMFILE* setup_fsb_streamfile(STREAMFILE* sf, const uint8_t* key, size_t key_size, int is_alt) {
|
||||
STREAMFILE* new_sf = NULL;
|
||||
fsb_decryption_data io_data = {0};
|
||||
size_t io_data_size = sizeof(fsb_decryption_data);
|
||||
|
||||
/* setup decryption with key (external) */
|
||||
if (!key_size || key_size > FSB_KEY_MAX)
|
||||
return NULL;
|
||||
|
||||
memcpy(io_data.key, key, key_size);
|
||||
io_data.key_size = key_size;
|
||||
io_data.is_alt = is_alt;
|
||||
|
||||
/* setup subfile */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data,io_data_size, fsb_decryption_read,NULL);
|
||||
new_sf = open_fakename_streamfile(new_sf, NULL,"fsb");
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _FSB5_STREAMFILE_H_ */
|
@ -137,7 +137,6 @@ VGMSTREAM * init_vgmstream_hca_subkey(STREAMFILE *streamFile, uint16_t subkey);
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
VGMSTREAM * init_vgmstream_ffmpeg(STREAMFILE *streamFile);
|
||||
VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size);
|
||||
|
||||
VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE * streamFile);
|
||||
#endif
|
||||
|
@ -21,12 +21,15 @@ VGMSTREAM* init_vgmstream_mups(STREAMFILE* sf) {
|
||||
goto fail;
|
||||
|
||||
/* just an Ogg with changed OggS/vorbis words (see streamfile) */
|
||||
|
||||
temp_sf = setup_mups_streamfile(sf, 0x08);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
vgmstream = init_vgmstream_ogg_vorbis(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
|
||||
|
@ -3792,6 +3792,19 @@ static int config_sb_version(ubi_sb_header* sb, STREAMFILE* sf) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Tom Clancy's Ghost Recon Advanced Warfighter 2 (2007)(X360)-bank */
|
||||
if (sb->version == 0x0018000b && sb->platform == UBI_X360) {
|
||||
config_sb_entry(sb, 0x68, 0x70);
|
||||
|
||||
config_sb_audio_fs(sb, 0x28, 0x2c, 0x30);
|
||||
config_sb_audio_he(sb, 0x3c, 0x40, 0x48, 0x50, 0x58, 0x5c);
|
||||
sb->cfg.audio_xma_offset = 0x68;
|
||||
|
||||
config_sb_sequence(sb, 0x2c, 0x14);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* TMNT (2007)(PSP)-map 0x00190001 */
|
||||
/* Surf's Up (2007)(PSP)-map 0x00190005 */
|
||||
if ((sb->version == 0x00190001 && sb->platform == UBI_PSP) ||
|
||||
@ -3888,6 +3901,18 @@ static int config_sb_version(ubi_sb_header* sb, STREAMFILE* sf) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Tom Clancy's Ghost Recon Advanced Warfighter 2 (2007)(PS3)-bank */
|
||||
if (sb->version == 0x001A0003 && sb->platform == UBI_PS3) {
|
||||
config_sb_entry(sb, 0x6c, 0x78);
|
||||
|
||||
config_sb_audio_fs(sb, 0x30, 0x34, 0x38);
|
||||
config_sb_audio_he(sb, 0x40, 0x44, 0x4c, 0x54, 0x5c, 0x60);
|
||||
|
||||
config_sb_sequence(sb, 0x2c, 0x14);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Cranium Kabookii (2007)(Wii)-bank */
|
||||
if (sb->version == 0x001A0003 && sb->platform == UBI_WII) {
|
||||
config_sb_entry(sb, 0x6c, 0x78);
|
||||
|
10
src/util.c
10
src/util.c
@ -61,8 +61,6 @@ void interleave_stereo(sample_t * buffer, int32_t sample_count) {
|
||||
else
|
||||
belongs = (tomove-sample_count)*2+1;
|
||||
|
||||
printf("move %d to %d\n",tomove,belongs);
|
||||
|
||||
temp = buffer[belongs];
|
||||
buffer[belongs] = moving;
|
||||
moving = temp;
|
||||
@ -100,6 +98,14 @@ void put_32bitBE(uint8_t * buf, int32_t i) {
|
||||
buf[3] = (uint8_t)(i & 0xFF);
|
||||
}
|
||||
|
||||
int round10(int val) {
|
||||
int round_val = val % 10;
|
||||
if (round_val < 5) /* half-down rounding */
|
||||
return val - round_val;
|
||||
else
|
||||
return val + (10 - round_val);
|
||||
}
|
||||
|
||||
void swap_samples_le(sample_t *buf, int count) {
|
||||
/* Windows can't be BE... I think */
|
||||
#if !defined(_WIN32)
|
||||
|
12
src/util.h
12
src/util.h
@ -7,6 +7,8 @@
|
||||
#ifndef _UTIL_H
|
||||
#define _UTIL_H
|
||||
|
||||
/* very common functions, so static inline in .h is useful to avoid some call overhead */
|
||||
|
||||
/* host endian independent multi-byte integer reading */
|
||||
|
||||
static inline int16_t get_16bitBE(uint8_t * p) {
|
||||
@ -91,13 +93,9 @@ static inline int clamp16(int32_t val) {
|
||||
else return val;
|
||||
}
|
||||
|
||||
static inline int round10(int val) {
|
||||
int round_val = val % 10;
|
||||
if (round_val < 5) /* half-down rounding */
|
||||
return val - round_val;
|
||||
else
|
||||
return val + (10 - round_val);
|
||||
}
|
||||
/* less common functions, no need to inline */
|
||||
|
||||
int round10(int val);
|
||||
|
||||
/* return a file's extension (a pointer to the first character of the
|
||||
* extension in the original filename or the ending null byte if no extension */
|
||||
|
@ -498,9 +498,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
|
||||
init_vgmstream_diva,
|
||||
init_vgmstream_imuse,
|
||||
init_vgmstream_ktsr,
|
||||
#ifdef VGM_USE_VORBIS
|
||||
init_vgmstream_mups,
|
||||
#endif
|
||||
init_vgmstream_kat,
|
||||
|
||||
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
|
||||
|
Loading…
Reference in New Issue
Block a user