Add Ubi LyN custom MP4 [Zombie U (WiiU)]

This commit is contained in:
bnnm 2021-12-11 15:02:21 +01:00
parent e56a761482
commit f681916ff0
3 changed files with 224 additions and 70 deletions

View File

@ -657,6 +657,7 @@ typedef struct {
} mp4_custom_t;
ffmpeg_codec_data* init_ffmpeg_mp4_custom_std(STREAMFILE* sf, mp4_custom_t* mp4);
ffmpeg_codec_data* init_ffmpeg_mp4_custom_lyn(STREAMFILE* sf, mp4_custom_t* mp4);
#endif

View File

@ -1,14 +1,16 @@
#include "coding.h"
#include "../streamfile.h"
#include <string.h>
#include "../meta/deblock_streamfile.h"
#ifdef VGM_USE_FFMPEG
typedef enum { MP4_STD, MP4_LYN } mp4_type_t;
/**
* Makes a MP4 header for MP4 raw data with a separate frame table, simulating a real MP4 that
* also has such table embedded in their custom chunks.
*/
//TODO: segfautls with certain audio files (ffmpeg buf?)
//TODO: segfaults with certain audio files (ffmpeg?)
/* *********************************************************** */
@ -26,6 +28,8 @@ typedef struct {
typedef struct {
STREAMFILE* sf;
mp4_custom_t* mp4; /* config */
mp4_type_t type;
uint8_t* out; /* current position */
int bytes; /* written bytes */
m4a_state_t chunks; /* chunks offsets are absolute, save position until we know header size */
@ -115,9 +119,36 @@ static void add_stsz(m4a_header_t* h) {
add_u32b(h, 0); /* Sample size (CBR) */
add_u32b(h, h->mp4->table_entries); /* Number of entries (VBR) */
for (i = 0; i < h->mp4->table_entries; i++) {
size = read_u32le(h->mp4->table_offset + i * 0x04, h->sf);
add_u32b(h, size); /* Sample N */
switch(h->type) {
case MP4_LYN: {
uint32_t curr_size, next_size;
/* LyN has a seek table with every frame, and frames are preprended by a 0x02
* frame header with frame size, so we can reconstruct a frame table */
for (i = 0; i < h->mp4->table_entries - 1; i++) {
curr_size = read_u32le(h->mp4->table_offset + (i + 0) * 0x04, h->sf);
next_size = read_u32le(h->mp4->table_offset + (i + 1) * 0x04, h->sf);
size = next_size - curr_size - 0x02;
add_u32b(h, size); /* Sample N */
//;VGM_LOG("%i: %x (%x: %x - %x - 0x02)\n", i, size, h->mp4->table_offset + (i + 1) * 0x04, next_size, curr_size);
}
curr_size = read_u32le(h->mp4->table_offset + (i + 0) * 0x04, h->sf);
next_size = h->mp4->stream_size; /* no last offset */
size = next_size - curr_size - 0x02;
add_u32b(h, size); /* Sample N */
//;VGM_LOG("%i: %x\n", i, size);
break;
}
default: {
for (i = 0; i < h->mp4->table_entries; i++) {
size = read_u32le(h->mp4->table_offset + i * 0x04, h->sf);
add_u32b(h, size); /* Sample N */
}
break;
}
}
}
@ -390,7 +421,7 @@ static void add_moov(m4a_header_t* h) {
/* *** */
static int make_m4a_header(uint8_t* buf, int buf_len, mp4_custom_t* mp4, STREAMFILE* sf) {
static int make_m4a_header(uint8_t* buf, int buf_len, mp4_custom_t* mp4, STREAMFILE* sf, mp4_type_t type) {
m4a_header_t h = {0};
if (buf_len < 0x400 + mp4->table_entries * 0x4) /* approx */
@ -398,6 +429,7 @@ static int make_m4a_header(uint8_t* buf, int buf_len, mp4_custom_t* mp4, STREAMF
h.sf = sf;
h.mp4 = mp4;
h.type = type;
h.out = buf;
add_ftyp(&h);
@ -416,8 +448,40 @@ fail:
/* ************************************************************************* */
ffmpeg_codec_data* init_ffmpeg_mp4_custom_std(STREAMFILE* sf, mp4_custom_t* mp4) {
static void block_callback(STREAMFILE* sf, deblock_io_data* data) {
data->data_size = read_u16be(data->physical_offset, sf);
data->skip_size = 0x02;
data->block_size = data->skip_size + data->data_size;
}
static STREAMFILE* setup_mp4_streamfile(STREAMFILE* sf, mp4_custom_t* mp4, mp4_type_t type) {
STREAMFILE* new_sf = NULL;
deblock_config_t cfg = {0};
cfg.stream_start = mp4->stream_offset;
cfg.stream_size = mp4->stream_size;
cfg.block_callback = block_callback;
switch(type) {
case MP4_LYN: /* each frame has a 0x02 header */
cfg.logical_size = mp4->stream_size - (mp4->table_entries * 0x02);
break;
default:
return NULL;
}
/* setup sf */
new_sf = open_wrap_streamfile(sf);
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
//new_sf = open_clamp_streamfile_f(new_sf, 0x00, clean_size);
return new_sf;
}
/* ************************************************************************* */
static ffmpeg_codec_data* init_ffmpeg_mp4_custom(STREAMFILE* sf, mp4_custom_t* mp4, mp4_type_t type) {
ffmpeg_codec_data* ffmpeg_data = NULL;
STREAMFILE* temp_sf = NULL;
int bytes;
uint8_t* buf = NULL;
int buf_len = 0x800 + mp4->table_entries * 0x4; /* approx max sum of atom chunks is ~0x400 */
@ -427,20 +491,48 @@ ffmpeg_codec_data* init_ffmpeg_mp4_custom_std(STREAMFILE* sf, mp4_custom_t* mp4)
buf = malloc(buf_len);
if (!buf) goto fail;
bytes = make_m4a_header(buf, buf_len, mp4, sf, type); /* before changing stream_offset/size */
bytes = make_m4a_header(buf, buf_len, mp4, sf);
ffmpeg_data = init_ffmpeg_header_offset(sf, buf, bytes, mp4->stream_offset, mp4->stream_size);
switch(type) {
case MP4_STD: /* regular raw data */
temp_sf = sf;
break;
case MP4_LYN: /* frames have size before them, but also a seek table */
temp_sf = setup_mp4_streamfile(sf, mp4, type);
mp4->stream_offset = 0;
mp4->stream_size = get_streamfile_size(temp_sf);
break;
default:
goto fail;
}
if (!temp_sf) goto fail;
ffmpeg_data = init_ffmpeg_header_offset(temp_sf, buf, bytes, mp4->stream_offset, mp4->stream_size);
if (!ffmpeg_data) goto fail;
/* not part of fake header since it's kinda complex to add (iTunes string comment) */
ffmpeg_set_skip_samples(ffmpeg_data, mp4->encoder_delay);
free(buf);
if (sf != temp_sf) close_streamfile(temp_sf);
return ffmpeg_data;
fail:
free(buf);
if (sf != temp_sf) close_streamfile(temp_sf);
free_ffmpeg(ffmpeg_data);
return NULL;
}
ffmpeg_codec_data* init_ffmpeg_mp4_custom_std(STREAMFILE* sf, mp4_custom_t* mp4) {
return init_ffmpeg_mp4_custom(sf, mp4, MP4_STD);
}
ffmpeg_codec_data* init_ffmpeg_mp4_custom_lyn(STREAMFILE* sf, mp4_custom_t* mp4) {
//TODO: most LyN files seem to give FFmpeg error in some frame, mono or stereo files,
// seek table correct and complete, no observed frame size/format/etc oddities.
// No audible issues though so maybe it's must some FFmpeg issue to be fixed there.
// (ex. frame 272 of 1162 in VO_ACT2_M12_FD_54_GILLI_PLS_0008479.Cafe_00000006.son)
return init_ffmpeg_mp4_custom(sf, mp4, MP4_LYN);
}
#endif

View File

@ -1,70 +1,98 @@
#include "meta.h"
#include "../layout/layout.h"
#include "../coding/coding.h"
#include "../util/chunks.h"
#include "ubi_lyn_streamfile.h"
/* LyN RIFF - from Ubisoft LyN engine games [Red Steel 2 (Wii), Adventures of Tintin (Multi), From Dust (Multi), Just Dance 3/4 (multi)] */
VGMSTREAM* init_vgmstream_ubi_lyn(STREAMFILE* sf) {
VGMSTREAM * vgmstream = NULL;
off_t start_offset, first_offset = 0xc;
off_t fmt_offset, data_offset, fact_offset;
size_t fmt_size, data_size, fact_size;
int loop_flag, channels, sample_rate, codec;
int num_samples;
VGMSTREAM* vgmstream = NULL;
off_t start_offset;
uint32_t fmt_offset = 0, fmt_size = 0, data_offset = 0, data_size = 0, fact_offset = 0, fact_size = 0;
int loop_flag = 0, channels = 0, sample_rate = 0, codec = 0;
int32_t num_samples = 0;
/* checks */
if (!is_id32be(0x00,sf, "RIFF"))
goto fail;
if (read_u32le(0x04,sf) + 0x04 + 0x04 != get_streamfile_size(sf))
goto fail;
if (!is_id32be(0x08,sf, "WAVE"))
goto fail;
/* .sns: Red Steel 2
* .wav: Tintin, Just Dance
* .son: From Dust */
* .son: From Dust, ZombieU */
if (!check_extensions(sf,"sns,wav,lwav,son"))
goto fail;
/* a slightly eccentric RIFF with custom codecs */
if (!is_id32be(0x00,sf, "RIFF") ||
!is_id32be(0x08,sf, "WAVE"))
goto fail;
if (read_32bitLE(0x04,sf) + 0x04 + 0x04 != get_streamfile_size(sf))
goto fail;
if (!find_chunk(sf, 0x666d7420,first_offset,0, &fmt_offset,&fmt_size, 0, 0)) /* "fmt " */
goto fail;
if (!find_chunk(sf, 0x64617461,first_offset,0, &data_offset,&data_size, 0, 0)) /* "data" */
goto fail;
/* always found, even with PCM (LyN subchunk seems to contain the engine version, ex. 0x0d/10) */
if (!find_chunk(sf, 0x66616374,first_offset,0, &fact_offset,&fact_size, 0, 0)) /* "fact" */
goto fail;
if (fact_size != 0x10 || read_32bitBE(fact_offset+0x04, sf) != 0x4C794E20) /* "LyN " */
goto fail;
num_samples = read_32bitLE(fact_offset+0x00, sf);
/* sometimes there is a LySE chunk */
/* parse format */
/* parse chunks (reads once linearly) */
{
if (fmt_size < 0x12)
goto fail;
codec = read_u16le(fmt_offset+0x00,sf);
channels = read_u16le(fmt_offset+0x02,sf);
sample_rate = read_s32le(fmt_offset+0x04,sf);
/* 0x08: average bytes, 0x0c: block align, 0x0e: bps, etc */
chunk_t rc = {0};
/* fake WAVEFORMATEX, used with > 2ch */
if (codec == 0xFFFE) {
if (fmt_size < 0x28)
goto fail;
/* fake GUID with first value doubling as codec */
codec = read_32bitLE(fmt_offset+0x18,sf);
if (read_32bitBE(fmt_offset+0x1c,sf) != 0x00001000 &&
read_32bitBE(fmt_offset+0x20,sf) != 0x800000AA &&
read_32bitBE(fmt_offset+0x24,sf) != 0x00389B71) {
goto fail;
rc.current = 0x0c;
while (next_chunk(&rc, sf)) {
switch(rc.type) {
case 0x666d7420: /* "fmt " */
fmt_offset = rc.offset;
fmt_size = rc.size;
if (fmt_size < 0x12)
goto fail;
codec = read_u16le(fmt_offset+0x00,sf);
channels = read_u16le(fmt_offset+0x02,sf);
sample_rate = read_s32le(fmt_offset+0x04,sf);
/* 0x08: average bytes, 0x0c: block align, 0x0e: bps, etc */
/* fake WAVEFORMATEX, used with > 2ch */
if (codec == 0xFFFE) {
if (fmt_size < 0x28)
goto fail;
/* fake GUID with first value doubling as codec */
codec = read_u32le(fmt_offset+0x18,sf);
if (read_u32be(fmt_offset+0x1c,sf) != 0x00001000 &&
read_u32be(fmt_offset+0x20,sf) != 0x800000AA &&
read_u32be(fmt_offset+0x24,sf) != 0x00389B71) {
goto fail;
}
}
break;
case 0x64617461: /* "data" */
data_offset = rc.offset;
data_size = rc.size;
break;
case 0x66616374: /* "fact" */
/* always found, even with PCM (LyN subchunk seems to contain the engine version, ex. 0x0d/10) */
fact_offset = rc.offset;
fact_size = rc.size;
if (fact_size != 0x10 || !is_id32be(fact_offset+0x04, sf, "LyN "))
goto fail;
num_samples = read_s32le(fact_offset+0x00, sf);
break;
case 0x4C795345: /* "LySE": optional, config? */
case 0x63756520: /* "cue ": total size cue? (rare) */
case 0x4C495354: /* "LIST": labels (rare) */
break;
default:
/* unknown chunk: must be another RIFF */
goto fail;
}
}
}
if (!fmt_offset || !fmt_size || !data_offset || !data_size || !fact_offset || !fact_size)
goto fail;
/* most songs simply repeat; loop if it looks long enough,
* but not too long (ex. Michael Jackson The Experience songs) */
loop_flag = (num_samples > 20*sample_rate && num_samples < 60*3*sample_rate); /* in seconds */
@ -116,10 +144,10 @@ VGMSTREAM* init_vgmstream_ubi_lyn(STREAMFILE* sf) {
layered_layout_data* data = NULL;
int i;
if (read_32bitLE(start_offset+0x00,sf) != 1) /* id? */
if (read_u32le(start_offset+0x00,sf) != 1) /* id? */
goto fail;
interleave_size = read_32bitLE(start_offset+0x04,sf);
interleave_size = read_u32le(start_offset+0x04,sf);
/* interleave is adjusted so there is no smaller last block, it seems */
vgmstream->coding_type = coding_OGG_VORBIS;
@ -133,7 +161,7 @@ VGMSTREAM* init_vgmstream_ubi_lyn(STREAMFILE* sf) {
/* open each layer subfile */
for (i = 0; i < channels; i++) {
STREAMFILE* temp_sf = NULL;
size_t logical_size = read_32bitLE(start_offset+0x08 + 0x04*i,sf);
size_t logical_size = read_u32le(start_offset+0x08 + 0x04*i,sf);
off_t layer_offset = start_offset + 0x08 + 0x04*channels; //+ interleave_size*i;
temp_sf = setup_ubi_lyn_streamfile(sf, layer_offset, interleave_size, i, channels, logical_size);
@ -208,6 +236,38 @@ VGMSTREAM* init_vgmstream_ubi_lyn(STREAMFILE* sf) {
#endif
#ifdef VGM_USE_FFMPEG
case 0x5052: { /* MP4 AAC (WiiU), custom */
mp4_custom_t mp4 = {0};
int entries;
/* 0x00: null? */
/* 0x04: fact samples again */
entries = read_s32le(start_offset + 0x08, sf);
/* has a seek/frame table then raw (non-header) AAC data */
mp4.channels = channels;
mp4.sample_rate = sample_rate;
mp4.num_samples = num_samples;
mp4.stream_offset = data_offset + (0x0c + entries * 0x04);
mp4.stream_size = data_size - (0x0c + entries * 0x04);
mp4.table_offset = data_offset + 0x0c;
mp4.table_entries = entries;
/* assumed (not in fmt's block size, fact, LyN, etc) */
mp4.encoder_delay = 1024; /* observed, uses libaac */
mp4.end_padding = 0;
mp4.frame_samples = 1024;
vgmstream->num_samples -= mp4.encoder_delay;
vgmstream->loop_end_sample -= mp4.encoder_delay;
vgmstream->codec_data = init_ffmpeg_mp4_custom_lyn(sf, &mp4);
if (!vgmstream->codec_data) goto fail;
vgmstream->coding_type = coding_FFmpeg;
vgmstream->layout_type = layout_none;
break;
}
case 0x0166: { /* XMA (X360), standard */
uint8_t buf[0x100];
int bytes;
@ -217,8 +277,8 @@ VGMSTREAM* init_vgmstream_ubi_lyn(STREAMFILE* sf) {
/* skip standard XMA header + seek table */
/* 0x00: version? no apparent differences (0x1=Just Dance 4, 0x3=others) */
chunk_offset = start_offset + 0x04 + 0x04;
chunk_size = read_32bitLE(start_offset + 0x04, sf);
seek_size = read_32bitLE(chunk_offset+chunk_size, sf);
chunk_size = read_u32le(start_offset + 0x04, sf);
seek_size = read_u32le(chunk_offset+chunk_size, sf);
start_offset += (0x04 + 0x04 + chunk_size + 0x04 + seek_size);
data_size -= (0x04 + 0x04 + chunk_size + 0x04 + seek_size);
@ -248,9 +308,9 @@ fail:
/* LyN RIFF in containers */
VGMSTREAM * init_vgmstream_ubi_lyn_container(STREAMFILE *sf) {
VGMSTREAM *vgmstream = NULL;
STREAMFILE *temp_sf = NULL;
VGMSTREAM* init_vgmstream_ubi_lyn_container(STREAMFILE* sf) {
VGMSTREAM* vgmstream = NULL;
STREAMFILE* temp_sf = NULL;
off_t subfile_offset;
size_t subfile_size;
@ -262,29 +322,30 @@ VGMSTREAM * init_vgmstream_ubi_lyn_container(STREAMFILE *sf) {
goto fail;
/* find "RIFF" position */
if (read_32bitBE(0x00,sf) == 0x4C795345 && /* "LySE" */
read_32bitBE(0x14,sf) == 0x52494646) { /* "RIFF" */
if (is_id32be(0x00,sf, "LySE") &&
is_id32be(0x14,sf, "RIFF")) {
subfile_offset = 0x14; /* Adventures of Tintin */
}
else if (read_32bitBE(0x20,sf) == 0x4C795345 && /* "LySE" */
read_32bitBE(0x34,sf) == 0x52494646) { /* "RIFF" */
else if (read_u32le(0x00,sf) + 0x22 == get_streamfile_size(sf) &&
is_id32be(0x20,sf, "LySE") &&
is_id32be(0x34,sf, "RIFF")) {
subfile_offset = 0x34; /* Michael Jackson The Experience (Wii) */
}
else if (read_32bitLE(0x00,sf)+0x20 == get_streamfile_size(sf) &&
read_32bitBE(0x20,sf) == 0x52494646) { /* "RIFF" */
subfile_offset = 0x20; /* Red Steel 2, From Dust */
else if (read_u32le(0x00,sf)+0x20 == get_streamfile_size(sf) &&
is_id32be(0x20,sf, "RIFF")) {
subfile_offset = 0x20; /* Red Steel 2, From Dust, ZombieU (also has "SON\0" at 0x18) */
}
else {
goto fail;
}
subfile_size = read_32bitLE(subfile_offset+0x04,sf) + 0x04+0x04;
subfile_size = read_u32le(subfile_offset+0x04,sf) + 0x04 + 0x04;
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, NULL);
if (!temp_sf) goto fail;
vgmstream = init_vgmstream_ubi_lyn(temp_sf);
close_streamfile(temp_sf);
return vgmstream;