mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-31 04:13:47 +01:00
Merge pull request #867 from bnnm/mp4-ww
- Fix Opus .wem with metadata [Gears 5 (PC)] - Add Ogg .msa [Metal Slug Attack (Android)] - Show .isb internal folder as stream name - Add CRI's looping MP4 [Imperial SaGa Eclipse (Browser)]
This commit is contained in:
commit
136a7f7c2b
@ -179,8 +179,10 @@ void free_speex(speex_codec_data* data) {
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
speex_decoder_destroy(data->state);
|
||||
speex_bits_destroy(&data->bits);
|
||||
if (data->state) {
|
||||
speex_decoder_destroy(data->state);
|
||||
speex_bits_destroy(&data->bits);
|
||||
}
|
||||
|
||||
free(data->samples);
|
||||
free(data);
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP, CWAC } awb_type;
|
||||
typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP, CWAC, M4A } awb_type;
|
||||
|
||||
static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid);
|
||||
|
||||
@ -120,6 +120,11 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
|
||||
type = CWAC;
|
||||
extension = "dsp";
|
||||
}
|
||||
else if (read_u32be(subfile_offset+0x00,sf) == 0x00000018 &&
|
||||
read_u32be(subfile_offset+0x04,sf) == 0x66747970) { /* chunk size + "ftyp" (type 19) */
|
||||
type = M4A;
|
||||
extension = "m4a";
|
||||
}
|
||||
else {
|
||||
VGM_LOG("AWB: unknown codec\n");
|
||||
goto fail;
|
||||
@ -158,6 +163,12 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
|
||||
vgmstream = init_vgmstream_dsp_cwac(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case M4A: /* Imperial SaGa Eclipse (Browser) */
|
||||
vgmstream = init_vgmstream_mp4_aac_ffmpeg(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
@ -3,10 +3,10 @@
|
||||
|
||||
|
||||
/* .ISB - Creative ISACT (Interactive Spatial Audio Composition Tools) middleware [Psychonauts (PC), Mass Effect (multi)] */
|
||||
VGMSTREAM * init_vgmstream_isb(STREAMFILE *sf) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset = 0, name_offset = 0;
|
||||
size_t stream_size = 0, name_size = 0;
|
||||
VGMSTREAM* init_vgmstream_isb(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset = 0, name_offset = 0, nfld_offset = 0;
|
||||
size_t stream_size = 0, name_size = 0, nfld_size = 0;
|
||||
int loop_flag = 0, channels = 0, sample_rate = 0, codec = 0, pcm_bytes = 0, bps = 0;
|
||||
int total_subsongs, target_subsong = sf->stream_index;
|
||||
uint32_t (*read_u32me)(off_t,STREAMFILE*);
|
||||
@ -50,9 +50,6 @@ VGMSTREAM * init_vgmstream_isb(STREAMFILE *sf) {
|
||||
uint32_t chunk_size = read_u32me(offset + 0x04,sf);
|
||||
offset += 0x08;
|
||||
|
||||
//VGM_LOG("offset=%lx, sub=%c%c%c%c\n", offset,(chunk_type>>24 & 0xFF), (chunk_type>>16 & 0xFF), (chunk_type>>8 & 0xFF), (chunk_type & 0xFF));
|
||||
|
||||
|
||||
switch(chunk_type) {
|
||||
case 0x4C495354: /* "LIST" */
|
||||
if (read_u32ce(offset, sf) == get_id32be("samp")) {
|
||||
@ -65,6 +62,8 @@ VGMSTREAM * init_vgmstream_isb(STREAMFILE *sf) {
|
||||
}
|
||||
else if (read_u32ce(offset, sf) == get_id32be("fldr")) {
|
||||
/* subfolder with another LIST inside, for example "stingers" > N smpl (seen in some music_bank) */
|
||||
off_t current_nfld_offset = 0;
|
||||
size_t current_nfld_size = 0;
|
||||
|
||||
suboffset = offset + 0x04;
|
||||
submax_offset = offset + chunk_size;
|
||||
@ -73,7 +72,12 @@ VGMSTREAM * init_vgmstream_isb(STREAMFILE *sf) {
|
||||
uint32_t subchunk_size = read_u32me(suboffset + 0x04,sf);
|
||||
suboffset += 0x08;
|
||||
|
||||
if (subchunk_type == get_id32be("LIST")) {
|
||||
if (subchunk_type == get_id32be("titl")) {
|
||||
/* should go first in fldr*/
|
||||
current_nfld_offset = suboffset;
|
||||
current_nfld_size = subchunk_size;
|
||||
}
|
||||
else if (subchunk_type == get_id32be("LIST")) {
|
||||
uint32_t subsubchunk_type = read_u32ce(suboffset, sf);
|
||||
|
||||
if (subsubchunk_type == get_id32be("samp")) {
|
||||
@ -81,6 +85,8 @@ VGMSTREAM * init_vgmstream_isb(STREAMFILE *sf) {
|
||||
if (target_subsong == total_subsongs && header_offset == 0) {
|
||||
header_offset = suboffset;
|
||||
header_size = chunk_size;
|
||||
nfld_offset = current_nfld_offset;
|
||||
nfld_size = current_nfld_size;
|
||||
}
|
||||
}
|
||||
else if (subsubchunk_type == get_id32be("fldr")) {
|
||||
@ -101,9 +107,7 @@ VGMSTREAM * init_vgmstream_isb(STREAMFILE *sf) {
|
||||
break;
|
||||
}
|
||||
|
||||
//if (offset + chunk_size+0x01 <= max_offset && chunk_size % 0x02)
|
||||
// chunk_size += 0x01;
|
||||
offset += chunk_size;
|
||||
offset += chunk_size; /* no chunk 1-byte padding */
|
||||
}
|
||||
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
@ -157,8 +161,6 @@ VGMSTREAM * init_vgmstream_isb(STREAMFILE *sf) {
|
||||
break;
|
||||
}
|
||||
|
||||
//if (offset + chunk_size+0x01 <= max_offset && chunk_size % 0x02)
|
||||
// chunk_size += 0x01;
|
||||
offset += chunk_size;
|
||||
}
|
||||
|
||||
@ -180,10 +182,27 @@ VGMSTREAM * init_vgmstream_isb(STREAMFILE *sf) {
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
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);
|
||||
if (name_offset) {
|
||||
/* UTF16 but only uses lower bytes */
|
||||
char name[256];
|
||||
char nfld[256];
|
||||
|
||||
/* should read string or set '\0' is no size is set/incorrect */
|
||||
if (name_size >= sizeof(name))
|
||||
name_size = sizeof(name) - 1;
|
||||
read_string_utf16(name, name_size, name_offset, sf, big_endian);
|
||||
|
||||
if (nfld_size >= sizeof(nfld))
|
||||
nfld_size = sizeof(nfld) - 1;
|
||||
read_string_utf16(nfld, nfld_size, nfld_offset, sf, big_endian);
|
||||
|
||||
if (nfld[0] && name[0]) {
|
||||
snprintf(vgmstream->stream_name,STREAM_NAME_SIZE, "%s/%s", nfld, name);
|
||||
}
|
||||
else if (name[0]) {
|
||||
snprintf(vgmstream->stream_name,STREAM_NAME_SIZE, "%s", name);
|
||||
}
|
||||
/* there is also a "titl" for the bank, but it's just the filename so probably unwanted */
|
||||
}
|
||||
|
||||
switch(codec) {
|
||||
|
241
src/meta/mp4.c
241
src/meta/mp4.c
@ -60,13 +60,13 @@ int mp4_file_close( void* handle )
|
||||
MP4FileProvider mp4_file_provider = { mp4_file_open, mp4_file_seek, mp4_file_read, mp4_file_write, mp4_file_close, mp4_file_get_size };
|
||||
|
||||
#ifdef VGM_USE_FDKAAC
|
||||
VGMSTREAM * init_vgmstream_mp4_aac_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size);
|
||||
VGMSTREAM * init_vgmstream_mp4_aac_offset(STREAMFILE *sf, uint64_t start, uint64_t size);
|
||||
|
||||
VGMSTREAM * init_vgmstream_mp4_aac(STREAMFILE *streamFile) {
|
||||
return init_vgmstream_mp4_aac_offset( streamFile, 0, streamFile->get_size(streamFile) );
|
||||
VGMSTREAM * init_vgmstream_mp4_aac(STREAMFILE *sf) {
|
||||
return init_vgmstream_mp4_aac_offset( sf, 0, sf->get_size(sf) );
|
||||
}
|
||||
|
||||
VGMSTREAM * init_vgmstream_mp4_aac_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) {
|
||||
VGMSTREAM * init_vgmstream_mp4_aac_offset(STREAMFILE *sf, uint64_t start, uint64_t size) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
|
||||
char filename[PATH_LIMIT];
|
||||
@ -81,7 +81,7 @@ VGMSTREAM * init_vgmstream_mp4_aac_offset(STREAMFILE *streamFile, uint64_t start
|
||||
|
||||
if ( !aac_file ) goto fail;
|
||||
|
||||
aac_file->if_file.streamfile = streamFile;
|
||||
aac_file->if_file.streamfile = sf;
|
||||
aac_file->if_file.start = start;
|
||||
aac_file->if_file.offset = start;
|
||||
aac_file->if_file.size = size;
|
||||
@ -124,9 +124,9 @@ VGMSTREAM * init_vgmstream_mp4_aac_offset(STREAMFILE *streamFile, uint64_t start
|
||||
aac_file->samples_per_frame = stream_info->frameSize;
|
||||
aac_file->samples_discard = 0;
|
||||
|
||||
streamFile->get_name( streamFile, filename, sizeof(filename) );
|
||||
sf->get_name( sf, filename, sizeof(filename) );
|
||||
|
||||
aac_file->if_file.streamfile = streamFile->open(streamFile, filename, STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
aac_file->if_file.streamfile = sf->open(sf, filename, STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (!aac_file->if_file.streamfile) goto fail;
|
||||
|
||||
vgmstream = allocate_vgmstream( stream_info->numChannels, 1 );
|
||||
@ -162,64 +162,64 @@ fail:
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
|
||||
static int find_atom_be(STREAMFILE *streamFile, uint32_t atom_id, off_t start_offset, off_t *out_atom_offset, size_t *out_atom_size);
|
||||
typedef struct {
|
||||
int32_t num_samples;
|
||||
int loop_flag;
|
||||
int32_t loop_start;
|
||||
int32_t loop_end;
|
||||
int32_t encoder_delay;
|
||||
} mp4_header;
|
||||
|
||||
static void parse_mp4(STREAMFILE* sf, mp4_header* mp4);
|
||||
|
||||
|
||||
VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
VGMSTREAM* init_vgmstream_mp4_aac_ffmpeg(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset = 0;
|
||||
int loop_flag = 0;
|
||||
int32_t loop_start_sample = 0, loop_end_sample = 0;
|
||||
size_t filesize;
|
||||
off_t atom_offset;
|
||||
size_t atom_size;
|
||||
|
||||
ffmpeg_codec_data *ffmpeg_data = NULL;
|
||||
mp4_header mp4 = {0};
|
||||
size_t file_size;
|
||||
ffmpeg_codec_data* ffmpeg_data = NULL;
|
||||
|
||||
|
||||
/* check extension, case insensitive */
|
||||
/* .bin: Final Fantasy Dimensions (iOS), Final Fantasy V (iOS)
|
||||
* .msd: UNO (iOS) */
|
||||
if (!check_extensions(streamFile,"mp4,m4a,m4v,lmp4,bin,msd"))
|
||||
/* checks */
|
||||
/* .bin: Final Fantasy Dimensions (iOS), Final Fantasy V (iOS)
|
||||
* .msd: UNO (iOS) */
|
||||
if (!check_extensions(sf,"mp4,m4a,m4v,lmp4,bin,lbin,msd"))
|
||||
goto fail;
|
||||
|
||||
filesize = streamFile->get_size(streamFile);
|
||||
|
||||
/* check header */
|
||||
if ( read_32bitBE(start_offset+0x04,streamFile) != 0x66747970) /* atom size @0x00 + "ftyp" @0x04 */
|
||||
if ((read_u32be(0x00,sf) & 0xFFFFFF00) != 0) /* first atom BE size (usually ~0x18) */
|
||||
goto fail;
|
||||
if (!is_id32be(0x04,sf, "ftyp"))
|
||||
goto fail;
|
||||
|
||||
ffmpeg_data = init_ffmpeg_offset(streamFile, start_offset, filesize);
|
||||
if ( !ffmpeg_data ) goto fail;
|
||||
file_size = get_streamfile_size(sf);
|
||||
|
||||
ffmpeg_data = init_ffmpeg_offset(sf, start_offset, file_size);
|
||||
if (!ffmpeg_data) goto fail;
|
||||
|
||||
parse_mp4(sf, &mp4);
|
||||
|
||||
/* Tales of Hearts iOS has loop info in the first "free" atom */
|
||||
if (find_atom_be(streamFile, 0x66726565, start_offset, &atom_offset, &atom_size)) { /* "free" */
|
||||
if (read_32bitBE(atom_offset,streamFile) == 0x4F700002
|
||||
&& (atom_size == 0x38 || atom_size == 0x40)) { /* make sure it's ToHr "free" */
|
||||
/* 0x00: id? 0x04/8: s_rate; 0x10: num_samples (without padding, same as FFmpeg's) */
|
||||
/* 0x14/18/1c: 0x238/250/278? 0x20: ? 0x24: start_pad */
|
||||
loop_flag = read_32bitBE(atom_offset+0x28,streamFile);
|
||||
if (loop_flag) { /* atom ends if no loop flag */
|
||||
loop_start_sample = read_32bitBE(atom_offset+0x2c,streamFile);
|
||||
loop_end_sample = read_32bitBE(atom_offset+0x30,streamFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(ffmpeg_data->channels,loop_flag);
|
||||
vgmstream = allocate_vgmstream(ffmpeg_data->channels, mp4.loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_MP4;
|
||||
vgmstream->sample_rate = ffmpeg_data->sampleRate;
|
||||
vgmstream->num_samples = mp4.num_samples;
|
||||
if (vgmstream->num_samples == 0)
|
||||
vgmstream->num_samples = ffmpeg_data->totalSamples;
|
||||
vgmstream->loop_start_sample = mp4.loop_start;
|
||||
vgmstream->loop_end_sample = mp4.loop_end;
|
||||
|
||||
vgmstream->codec_data = ffmpeg_data;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->meta_type = meta_MP4;
|
||||
|
||||
vgmstream->sample_rate = ffmpeg_data->sampleRate;
|
||||
vgmstream->num_samples = ffmpeg_data->totalSamples;
|
||||
vgmstream->loop_start_sample = loop_start_sample;
|
||||
vgmstream->loop_end_sample = loop_end_sample;
|
||||
vgmstream->num_streams = ffmpeg_data->streamCount; /* may contain N tracks */
|
||||
|
||||
vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data);
|
||||
if (mp4.encoder_delay)
|
||||
ffmpeg_set_skip_samples(vgmstream->codec_data, mp4.encoder_delay);
|
||||
|
||||
return vgmstream;
|
||||
|
||||
@ -232,31 +232,138 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Almost the same as streamfile.c's find_chunk but for "atom" chunks, which have chunk_size first because Apple, returns 0 on failure */
|
||||
static int find_atom_be(STREAMFILE *streamFile, uint32_t atom_id, off_t start_offset, off_t *out_atom_offset, size_t *out_atom_size) {
|
||||
size_t filesize;
|
||||
off_t current_atom = start_offset;
|
||||
int full_atom_size = 1;
|
||||
int size_big_endian = 1;
|
||||
/* read useful MP4 chunks */
|
||||
static void parse_mp4(STREAMFILE* sf, mp4_header* mp4) {
|
||||
off_t offset, suboffset, max_offset, max_suboffset;
|
||||
|
||||
filesize = get_streamfile_size(streamFile);
|
||||
/* read chunks */
|
||||
while (current_atom < filesize) {
|
||||
off_t chunk_size = size_big_endian ?
|
||||
read_32bitBE(current_atom+0,streamFile) :
|
||||
read_32bitLE(current_atom+0,streamFile);
|
||||
uint32_t chunk_type = read_32bitBE(current_atom+4,streamFile);
|
||||
|
||||
if (chunk_type == atom_id) {
|
||||
if (out_atom_size) *out_atom_size = chunk_size;
|
||||
if (out_atom_offset) *out_atom_offset = current_atom+8;
|
||||
return 1;
|
||||
/* MOV format chunks, called "atoms", size goes first because Apple */
|
||||
offset = 0x00;
|
||||
max_offset = get_streamfile_size(sf);
|
||||
while (offset < max_offset) {
|
||||
uint32_t size = read_u32be(offset + 0x00,sf);
|
||||
uint32_t type = read_u32be(offset + 0x04,sf);
|
||||
//offset += 0x08;
|
||||
|
||||
/* just in case */
|
||||
if (size == 0)
|
||||
break;
|
||||
|
||||
switch(type) {
|
||||
case 0x66726565: /* "free" */
|
||||
/* Tales of Hearts R (iOS) has loop info in the first "free" atom */
|
||||
if (read_u32be(offset + 0x08,sf) == 0x4F700002 && (size == 0x38 || size == 0x40)) {
|
||||
/* 0x00: id / "Op" */
|
||||
/* 0x02: channels */
|
||||
/* 0x04/8: sample rate */
|
||||
/* 0x0c: null? */
|
||||
/* 0x10: num_samples (without padding, same as FFmpeg's) */
|
||||
/* 0x14/18/1c/20: offsets to stream info (stts/stsc/stsz/stco) */
|
||||
mp4->encoder_delay = read_u32be(offset + 0x08 + 0x24,sf); /* Apple's 2112 */
|
||||
mp4->loop_flag = read_u32be(offset + 0x08 + 0x28,sf);
|
||||
if (mp4->loop_flag) { /* atom ends if no loop flag */
|
||||
mp4->loop_start = read_u32be(offset + 0x08 + 0x2c,sf);
|
||||
mp4->loop_end = read_u32be(offset + 0x08 + 0x30,sf);
|
||||
}
|
||||
/* could stop reading since FFmpeg will too */
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x6D6F6F76: { /* "moov" (header) */
|
||||
suboffset = offset += 0x08;
|
||||
max_suboffset = offset + size;
|
||||
while (suboffset < max_suboffset) {
|
||||
uint32_t subsize = read_u32be(suboffset + 0x00,sf);
|
||||
uint32_t subtype = read_u32be(suboffset + 0x04,sf);
|
||||
|
||||
/* padded in ToRR */
|
||||
if (subsize == 0)
|
||||
break;
|
||||
|
||||
switch(subtype) {
|
||||
case 0x75647461: /* "udta" */
|
||||
/* CRI subchunk [Imperial SaGa Eclipse (Browser)]
|
||||
* incidentally "moov" header comes after data ("mdat") in CRI's files */
|
||||
if (subsize >= 0x28 && is_id32be(suboffset + 0x08 + 0x04,sf, "criw")) {
|
||||
off_t criw_offset = suboffset + 0x08 + 0x08;
|
||||
|
||||
mp4->loop_start = read_s32be(criw_offset + 0x00,sf);
|
||||
mp4->loop_end = read_s32be(criw_offset + 0x04,sf);
|
||||
mp4->encoder_delay = read_s32be(criw_offset + 0x08,sf); /* Apple's 2112 */
|
||||
mp4->num_samples = read_s32be(criw_offset + 0x0c,sf);
|
||||
mp4->loop_flag = (mp4->loop_end > 0);
|
||||
/* next 2 fields are null */
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
suboffset += subsize;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
current_atom += full_atom_size ? chunk_size : 4+4+chunk_size;
|
||||
offset += size; /* atoms don't seem to need to padding byte, unlike RIFF */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* CRI's encryption info (for lack of a better place) [Final Fantasy Digital Card Game (Browser)]
|
||||
*
|
||||
* Like other CRI stuff their MP4 can be encrypted, from file's beginning (including headers).
|
||||
* This is more or less how data is decrypted (supposedly, from decompilations), for reference:
|
||||
*/
|
||||
#if 0
|
||||
void criAacCodec_SetDecryptionKey(uint64_t keycode, uint16_t* key) {
|
||||
if (!keycode)
|
||||
return;
|
||||
uint16_t k0 = 4 * ((keycode >> 0) & 0x0FFF) | 1;
|
||||
uint16_t k1 = 2 * ((keycode >> 12) & 0x1FFF) | 1;
|
||||
uint16_t k2 = 4 * ((keycode >> 25) & 0x1FFF) | 1;
|
||||
uint16_t k3 = 2 * ((keycode >> 38) & 0x3FFF) | 1;
|
||||
|
||||
key[0] = k0 ^ k1;
|
||||
key[1] = k1 ^ k2;
|
||||
key[2] = k2 ^ k3;
|
||||
key[3] = ~k3;
|
||||
|
||||
/* criatomexacb_generate_aac_decryption_key is slightly different, unsure which one is used: */
|
||||
//key[0] = k0 ^ k3;
|
||||
//key[1] = k2 ^ k3;
|
||||
//key[2] = k2 ^ k3;
|
||||
//key[3] = ~k3;
|
||||
}
|
||||
|
||||
void criAacCodec_DecryptData(const uint16_t* key, uint8_t* data, uint32_t size) {
|
||||
if (data_size)
|
||||
return;
|
||||
uint16_t seed0 = ~key[3];
|
||||
uint16_t seed1 = seed0 ^ key[2];
|
||||
uint16_t seed2 = seed1 ^ key[1];
|
||||
uint16_t seed3 = seed2 ^ key[0];
|
||||
|
||||
uint16_t xor = 2 * seed0 | 1;
|
||||
uint16_t add = 2 * seed0 | 1; /* not seed1 */
|
||||
uint16_t mul = 4 * seed2 | 1;
|
||||
|
||||
for (int i = 0; i < data_size; i++) {
|
||||
|
||||
if (!(uint16_t)i) { /* every 0x10000, without modulo */
|
||||
mul = (4 * seed2 + seed3 * (mul & 0xFFFC)) & 0xFFFD | 1;
|
||||
add = (2 * seed0 + seed1 * (add & 0xFFFE)) | 1;
|
||||
}
|
||||
xor = xor * mul + add;
|
||||
|
||||
*data ^= (xor >> 8) & 0xFF;
|
||||
++data;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -137,8 +137,9 @@ VGMSTREAM* init_vgmstream_ogg_vorbis(STREAMFILE* sf) {
|
||||
* .rof: The Rhythm of Fighters (Mobile)
|
||||
* .acm: Planescape Torment Enhanced Edition (PC)
|
||||
* .sod: Zone 4 (PC)
|
||||
* .msa: Metal Slug Attack (Mobile)
|
||||
* .aif/laif/aif-Loop: Psychonauts (PC) raw extractions (named) */
|
||||
if (check_extensions(sf,"ogg,logg,adx,rof,acm,sod,aif,laif,aif-Loop")) {
|
||||
if (check_extensions(sf,"ogg,logg,adx,rof,acm,sod,msa,aif,laif,aif-Loop")) {
|
||||
is_ogg = 1;
|
||||
} else if (check_extensions(sf,"um3")) {
|
||||
is_um3 = 1;
|
||||
|
@ -471,7 +471,7 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
break;
|
||||
}
|
||||
|
||||
case OPUS: { /* fully standard Ogg Opus [Girl Cafe Gun (Mobile)] */
|
||||
case OPUS: { /* fully standard Ogg Opus [Girl Cafe Gun (Mobile), Gears 5 (PC)] */
|
||||
if (ww.block_align != 0 || ww.bits_per_sample != 0) goto fail;
|
||||
|
||||
/* extra: size 0x12 */
|
||||
@ -486,6 +486,15 @@ VGMSTREAM* init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
ww.data_size = ww.file_size - start_offset;
|
||||
}
|
||||
|
||||
/* mutant .wem with metadata (voice strings/etc) at data start [Gears 5 (PC)] */
|
||||
if (ww.meta_offset) {
|
||||
/* 0x00: original setup_offset? (0x00 for Opus) */
|
||||
uint32_t meta_skip = read_u32(ww.meta_offset + 0x04, sf);
|
||||
|
||||
ww.data_offset += meta_skip;
|
||||
ww.data_size -= meta_skip;
|
||||
}
|
||||
|
||||
vgmstream->codec_data = init_ffmpeg_offset(sf, ww.data_offset, ww.data_size);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
|
Loading…
x
Reference in New Issue
Block a user