mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-15 02:57:38 +01:00
commit
bbeb4e4ba3
@ -18,6 +18,9 @@ Help and newest builds can be found here: https://www.hcs64.com/
|
||||
|
||||
Latest development is usually here: https://github.com/losnoco/vgmstream/
|
||||
|
||||
Latest releases are here: https://github.com/losnoco/vgmstream/releases
|
||||
Automated builds with the latest changes: https://ci.appveyor.com/project/kode54/vgmstream/branch/master/artifacts
|
||||
|
||||
You can find further info about other details in https://github.com/losnoco/vgmstream/tree/master/doc
|
||||
|
||||
## Needed extra files (for Windows)
|
||||
@ -423,6 +426,7 @@ are used in few games.
|
||||
- Circus XPCM ADPCM
|
||||
- OKI 4-bit ADPCM (16-bit output, 4-shift, PC-FX)
|
||||
- Ubisoft 4/6-bit ADPCM
|
||||
- Tiger Game.com ADPCM
|
||||
- SDX2 2:1 Squareroot-Delta-Exact compression DPCM
|
||||
- CBD2 2:1 Cuberoot-Delta-Exact compression DPCM
|
||||
- Activision EXAKT SASSC DPCM
|
||||
@ -430,11 +434,12 @@ are used in few games.
|
||||
- InterPlay ACM
|
||||
- VisualArt's NWA
|
||||
- Electronic Arts MicroTalk a.k.a. UTK or UMT
|
||||
- Relic Codec
|
||||
- CRI HCA
|
||||
- Xiph Vorbis (Ogg, FSB5, Wwise, OGL, Silicon Knights)
|
||||
- MPEG MP1/2/3 (standard, AHX, XVAG, FSB, AWC, P3D, etc)
|
||||
- ITU-T G.722.1 annex C (Polycom Siren 14)
|
||||
- ITU G.719 annex B (Polycom Siren 22)
|
||||
- ITU-T G.719 annex B (Polycom Siren 22)
|
||||
- Electronic Arts EALayer3
|
||||
- Electronic Arts EA-XMA
|
||||
- Sony ATRAC3, ATRAC3plus
|
||||
|
1682
doc/TXTH.md
1682
doc/TXTH.md
File diff suppressed because it is too large
Load Diff
@ -271,6 +271,7 @@ static const char* extension_list[] = {
|
||||
"lpcm",
|
||||
"lpk",
|
||||
"lps",
|
||||
"lrmb",
|
||||
"lse",
|
||||
"lsf",
|
||||
"lstm", //fake extension for .stm
|
||||
@ -1234,7 +1235,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_XPCM, "Circus XPCM header"},
|
||||
{meta_MSF_TAMASOFT, "Tama-Soft MSF header"},
|
||||
{meta_XPS_DAT, "From Software .XPS+DAT header"},
|
||||
{meta_ZSND, "Vicarious Visions ZSND header"},
|
||||
{meta_ZSND, "Z-Axis ZSND header"},
|
||||
{meta_DSP_ADPY, "AQUASTYLE ADPY header"},
|
||||
{meta_DSP_ADPX, "AQUASTYLE ADPX header"},
|
||||
{meta_OGG_OPUS, "Ogg Opus header"},
|
||||
@ -1266,6 +1267,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_FDA, "Relic FDA header"},
|
||||
{meta_TGC, "Tiger Game.com .4 header"},
|
||||
{meta_KWB, "Koei Tecmo WaveBank header"},
|
||||
{meta_LRMD, "Sony LRMD header"},
|
||||
|
||||
};
|
||||
|
||||
|
@ -256,6 +256,10 @@
|
||||
RelativePath=".\meta\cri_utf.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\deblock_streamfile.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\ea_eaac_streamfile.h"
|
||||
>
|
||||
@ -284,6 +288,10 @@
|
||||
RelativePath=".\meta\kma9_streamfile.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\lrmd_streamfile.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\mta2_streamfile.h"
|
||||
>
|
||||
@ -572,10 +580,14 @@
|
||||
RelativePath=".\meta\dc_asd.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\dcs_wav.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\dcs_wav.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\deblock_streamfile.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\dc_idvi.c"
|
||||
>
|
||||
@ -787,6 +799,10 @@
|
||||
<File
|
||||
RelativePath=".\meta\kwb.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\lrmd.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\lsf.c"
|
||||
|
@ -106,6 +106,7 @@
|
||||
<ClInclude Include="meta\bar_streamfile.h" />
|
||||
<ClInclude Include="meta\bgw_streamfile.h" />
|
||||
<ClInclude Include="meta\cri_utf.h" />
|
||||
<ClInclude Include="meta\deblock_streamfile.h" />
|
||||
<ClInclude Include="meta\ea_eaac_streamfile.h" />
|
||||
<ClInclude Include="meta\ea_eaac_opus_streamfile.h" />
|
||||
<ClInclude Include="meta\ea_schl_streamfile.h" />
|
||||
@ -113,6 +114,7 @@
|
||||
<ClInclude Include="meta\fsb5_interleave_streamfile.h" />
|
||||
<ClInclude Include="meta\jstm_streamfile.h" />
|
||||
<ClInclude Include="meta\kma9_streamfile.h" />
|
||||
<ClInclude Include="meta\lrmd_streamfile.h" />
|
||||
<ClInclude Include="meta\ppst_streamfile.h" />
|
||||
<ClInclude Include="meta\vsv_streamfile.h" />
|
||||
<ClInclude Include="meta\xavs_streamfile.h" />
|
||||
@ -177,6 +179,7 @@
|
||||
<ClCompile Include="meta\ios_psnd.c" />
|
||||
<ClCompile Include="meta\ktss.c" />
|
||||
<ClCompile Include="meta\kwb.c" />
|
||||
<ClCompile Include="meta\lrmd.c" />
|
||||
<ClCompile Include="meta\lsf.c" />
|
||||
<ClCompile Include="meta\mattel_hyperscan.c" />
|
||||
<ClCompile Include="meta\mib_mih.c" />
|
||||
@ -279,6 +282,7 @@
|
||||
<ClCompile Include="meta\cstr.c" />
|
||||
<ClCompile Include="meta\dc_asd.c" />
|
||||
<ClCompile Include="meta\dcs_wav.c" />
|
||||
<ClCompile Include="meta\deblock_streamfile.c" />
|
||||
<ClCompile Include="meta\dc_idvi.c" />
|
||||
<ClCompile Include="meta\dc_kcey.c" />
|
||||
<ClCompile Include="meta\dc_str.c" />
|
||||
|
@ -89,6 +89,9 @@
|
||||
<ClInclude Include="meta\cri_utf.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta\deblock_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta\ea_eaac_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
@ -110,6 +113,9 @@
|
||||
<ClInclude Include="meta\kma9_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta\lrmd_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta\mta2_streamfile.h">
|
||||
<Filter>meta\Header Files</Filter>
|
||||
</ClInclude>
|
||||
@ -376,6 +382,9 @@
|
||||
<ClCompile Include="meta\dcs_wav.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\deblock_streamfile.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\dc_idvi.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -1672,6 +1681,9 @@
|
||||
<ClCompile Include="meta\kwb.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\lrmd.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\ps2_va3.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
@ -1,133 +1,51 @@
|
||||
#ifndef _AIX_STREAMFILE_H_
|
||||
#define _AIX_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
off_t stream_offset;
|
||||
size_t stream_size;
|
||||
int layer_number;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake offset */
|
||||
off_t physical_offset; /* actual offset */
|
||||
size_t block_size; /* current size */
|
||||
size_t skip_size; /* size from block start to reach data */
|
||||
size_t data_size; /* usable size in a block */
|
||||
|
||||
size_t logical_size;
|
||||
} aix_io_data;
|
||||
|
||||
|
||||
static size_t aix_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, aix_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
|
||||
/* re-start when previous offset (can't map logical<>physical offsets) */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
}
|
||||
|
||||
/* read blocks */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
uint32_t block_id = read_u32be(data->physical_offset+0x00, streamfile);
|
||||
data->block_size = read_u32be(data->physical_offset+0x04, streamfile) + 0x08;
|
||||
|
||||
/* check valid block "AIXP" id, knowing that AIX segments end with "AIXE" block too */
|
||||
if (block_id != 0x41495850 || data->block_size == 0 || data->block_size == 0xFFFFFFFF) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* read target layer, otherwise skip to next block and try again */
|
||||
if (read_s8(data->physical_offset+0x08, streamfile) == data->layer_number) {
|
||||
/* 0x09(1): layer count */
|
||||
data->data_size = read_s16be(data->physical_offset+0x0a, streamfile);
|
||||
/* 0x0c: -1 */
|
||||
data->skip_size = 0x10;
|
||||
}
|
||||
|
||||
/* strange AIX in Tetris Collection (PS2) with padding before ADX start (no known flag) */
|
||||
if (data->logical_offset == 0x00 &&
|
||||
read_u32be(data->physical_offset + 0x10, streamfile) == 0 &&
|
||||
read_u16be(data->physical_offset + data->block_size - 0x28, streamfile) == 0x8000) {
|
||||
data->data_size = 0x28;
|
||||
data->skip_size = data->block_size - 0x28;
|
||||
}
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t aix_io_size(STREAMFILE *streamfile, aix_io_data* data) {
|
||||
uint8_t buf[1];
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
aix_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* Handles deinterleaving of AIX blocked layer streams */
|
||||
static STREAMFILE* setup_aix_streamfile(STREAMFILE *sf, off_t stream_offset, size_t stream_size, int layer_number, const char* extension) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
aix_io_data io_data = {0};
|
||||
|
||||
io_data.stream_offset = stream_offset;
|
||||
io_data.stream_size = stream_size;
|
||||
io_data.layer_number = layer_number;
|
||||
io_data.logical_offset = -1; /* force reset */
|
||||
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(aix_io_data), aix_io_read, aix_io_size);
|
||||
new_sf = open_buffer_streamfile_f(new_sf, 0);
|
||||
if (extension) {
|
||||
new_sf = open_fakename_streamfile_f(new_sf, NULL, extension);
|
||||
}
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _AIX_STREAMFILE_H_ */
|
||||
#ifndef _AIX_STREAMFILE_H_
|
||||
#define _AIX_STREAMFILE_H_
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
//todo block size must check >= stream_size
|
||||
|
||||
static void block_callback(STREAMFILE *sf, deblock_io_data *data) {
|
||||
uint32_t block_id = read_u32be(data->physical_offset + 0x00, sf);
|
||||
data->block_size = read_u32be(data->physical_offset + 0x04, sf) + 0x08;
|
||||
|
||||
/* check "AIXP" id, (AIX segments end with "AIXE" too) */
|
||||
if (block_id != 0x41495850) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* read target layer, otherwise ignore block */
|
||||
if (read_s8(data->physical_offset + 0x08, sf) == data->cfg.track_number) {
|
||||
/* 0x09(1): layer count */
|
||||
data->data_size = read_s16be(data->physical_offset + 0x0a, sf);
|
||||
/* 0x0c: -1 */
|
||||
data->skip_size = 0x10;
|
||||
}
|
||||
|
||||
/* strange AIX in Tetris Collection (PS2) with padding before ADX start (no known flag) */
|
||||
if (data->logical_offset == 0x00 &&
|
||||
read_u32be(data->physical_offset + 0x10, sf) == 0 &&
|
||||
read_u16be(data->physical_offset + data->block_size - 0x28, sf) == 0x8000) {
|
||||
data->data_size = 0x28;
|
||||
data->skip_size = data->block_size - 0x28;
|
||||
}
|
||||
}
|
||||
|
||||
/* Deinterleaves AIX layers */
|
||||
static STREAMFILE* setup_aix_streamfile(STREAMFILE *sf, off_t stream_offset, size_t stream_size, int layer_number, const char* extension) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
deblock_config_t cfg = {0};
|
||||
|
||||
cfg.stream_start = stream_offset;
|
||||
cfg.stream_size = stream_size;
|
||||
cfg.track_number = layer_number;
|
||||
cfg.block_callback = block_callback;
|
||||
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
|
||||
//new_sf = open_buffer_streamfile_f(new_sf, 0);
|
||||
if (extension)
|
||||
new_sf = open_fakename_streamfile_f(new_sf, NULL, extension);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _AIX_STREAMFILE_H_ */
|
||||
|
181
src/meta/deblock_streamfile.c
Normal file
181
src/meta/deblock_streamfile.c
Normal file
@ -0,0 +1,181 @@
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
//todo move to utils or something
|
||||
|
||||
static void block_callback_default(STREAMFILE *sf, deblock_io_data *data) {
|
||||
data->block_size = data->cfg.chunk_size;
|
||||
data->skip_size = data->cfg.skip_size;
|
||||
data->data_size = data->block_size - data->skip_size;
|
||||
|
||||
//;VGM_LOG("DEBLOCK: of=%lx, bs=%lx, ss=%lx, ds=%lx\n", data->physical_offset, data->block_size, data->skip_size, data->data_size);
|
||||
}
|
||||
|
||||
static size_t deblock_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, deblock_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
|
||||
/* re-start when previous offset (can't map logical<>physical offsets) */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
;VGM_LOG("DEBLOCK: restart offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset);
|
||||
data->physical_offset = data->cfg.stream_start;
|
||||
data->logical_offset = 0x00;
|
||||
data->block_size = 0;
|
||||
data->data_size = 0;
|
||||
data->skip_size = 0;
|
||||
|
||||
data->step_count = data->cfg.step_start;
|
||||
/*
|
||||
data->read_count = data->cfg.read_count;
|
||||
*/
|
||||
}
|
||||
|
||||
/* read blocks */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (offset < 0 ||
|
||||
(data->physical_offset >= data->cfg.stream_start + data->physical_size) ||
|
||||
(data->logical_size > 0 && offset > data->logical_size)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size <= 0) {
|
||||
data->cfg.block_callback(sf, data);
|
||||
|
||||
if (data->block_size <= 0) {
|
||||
VGM_LOG("DEBLOCK: block size not set at %lx\n", data->physical_offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if 1
|
||||
if (data->step_count > 0) {
|
||||
data->step_count--;
|
||||
data->physical_offset += data->block_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
/* handle blocks from multiple streams */
|
||||
{
|
||||
if (data->step_count > 0) {
|
||||
data->step_count--;
|
||||
data->data_size = 0; /* step over this block */
|
||||
}
|
||||
else if (data->read_count) {//must detect when blocks has been read
|
||||
data->read_count--; /* read this block */
|
||||
|
||||
/* reset */
|
||||
if (data->step_count == 0 && data->read_count == 0) {
|
||||
data->step_count = data->cfg.step_count;
|
||||
data->read_count = data->cfg.read_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
|
||||
data->step_count = data->cfg.step_count;
|
||||
//VGM_LOG("ignore at %lx + %lx, skips=%i, reads=%i\n", data->physical_offset, data->block_size, data->step_count, data->read_count);
|
||||
continue;
|
||||
}
|
||||
|
||||
//VGM_LOG("accept at %lx + %lx, skips=%i, reads=%i\n", data->physical_offset, data->block_size, data->step_count, data->read_count);
|
||||
|
||||
/* read block data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, sf);
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t deblock_io_size(STREAMFILE *streamfile, deblock_io_data* data) {
|
||||
uint8_t buf[0x04];
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
if (data->cfg.logical_size) {
|
||||
data->logical_size = data->cfg.logical_size;
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
deblock_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
|
||||
//todo tests:
|
||||
//if (logical_size > max_physical_offset)
|
||||
// return 0;
|
||||
//if (logical_size != data->stream_size)
|
||||
// return 0;
|
||||
|
||||
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* generic "de-blocker" helper for streams divided in blocks that have weird interleaves, their
|
||||
* decoder can't easily use blocked layout, or some other weird feature. It "filters" data so
|
||||
* reader only sees clean data without blocks. Must pass setup config and a callback that sets
|
||||
* sizes of a single block. */
|
||||
STREAMFILE* open_io_deblock_streamfile_f(STREAMFILE *sf, deblock_config_t *cfg) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
deblock_io_data io_data = {0};
|
||||
|
||||
/* prepare data */
|
||||
io_data.cfg = *cfg; /* memcpy */
|
||||
|
||||
if (io_data.cfg.block_callback == NULL)
|
||||
io_data.cfg.block_callback = block_callback_default;
|
||||
|
||||
if (io_data.cfg.stream_start < 0)
|
||||
goto fail;
|
||||
if (io_data.cfg.step_start < 0 || io_data.cfg.step_count < 0)
|
||||
goto fail;
|
||||
|
||||
if (io_data.cfg.step_count > 0) {
|
||||
io_data.cfg.step_count--;
|
||||
}
|
||||
/*
|
||||
if (io_data.cfg.read_count == 0)
|
||||
io_data.cfg.read_count = 1;
|
||||
*/
|
||||
io_data.physical_size = io_data.cfg.stream_size;
|
||||
if (io_data.physical_size > get_streamfile_size(sf) + io_data.cfg.stream_start || io_data.physical_size == 0)
|
||||
io_data.physical_size = get_streamfile_size(sf) - io_data.cfg.stream_start;
|
||||
io_data.physical_end = io_data.cfg.stream_start + io_data.physical_size;
|
||||
|
||||
io_data.logical_offset = -1; /* read reset */
|
||||
|
||||
//TODO: other validations
|
||||
|
||||
/* setup subfile */
|
||||
new_sf = open_io_streamfile_f(sf, &io_data, sizeof(deblock_io_data), deblock_io_read, deblock_io_size);
|
||||
return new_sf;
|
||||
fail:
|
||||
VGM_LOG("DEBLOCK: bad init\n");
|
||||
close_streamfile(sf);
|
||||
return NULL;
|
||||
}
|
61
src/meta/deblock_streamfile.h
Normal file
61
src/meta/deblock_streamfile.h
Normal file
@ -0,0 +1,61 @@
|
||||
#ifndef _DEBLOCK_STREAMFILE_H_
|
||||
#define _DEBLOCK_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
typedef struct deblock_config_t deblock_config_t;
|
||||
typedef struct deblock_io_data deblock_io_data;
|
||||
|
||||
struct deblock_config_t {
|
||||
/* config (all optional) */
|
||||
size_t logical_size; /* pre-calculated size for performance (otherwise has to read the whole thing) */
|
||||
off_t stream_start; /* data start */
|
||||
size_t stream_size; /* data max */
|
||||
|
||||
size_t chunk_size; /* some size like a constant interleave */
|
||||
size_t frame_size; /* some other size */
|
||||
size_t skip_size; /* same */
|
||||
|
||||
int codec; /* codec or type variations */
|
||||
int channels;
|
||||
int big_endian;
|
||||
uint32_t config; /* some non-standard config value */
|
||||
|
||||
/* read=blocks from out stream to read) and "steps" (blocks from other streams to skip) */
|
||||
int step_start; /* initial step_count at stream start (often 0) */
|
||||
int step_count; /* number of blocks to step over from other streams */
|
||||
//int read_count; /* number of blocks to read from this stream, after steps */
|
||||
|
||||
size_t track_size;
|
||||
int track_number;
|
||||
int track_count;
|
||||
uint32_t track_type;
|
||||
|
||||
size_t interleave_count;
|
||||
size_t interleave_last_count;
|
||||
|
||||
/* callback that setups deblock_io_data state, normally block_size and data_size */
|
||||
void (*block_callback)(STREAMFILE *sf, deblock_io_data *data);
|
||||
} ;
|
||||
|
||||
struct deblock_io_data {
|
||||
/* initial config */
|
||||
deblock_config_t cfg;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake deblocked offset */
|
||||
off_t physical_offset; /* actual file offset */
|
||||
off_t block_size; /* current block (added to physical offset) */
|
||||
off_t skip_size; /* data to skip from block start to reach data (like a header) */
|
||||
off_t data_size; /* usable data in a block (added to logical offset) */
|
||||
|
||||
int step_count; /* number of blocks to step over */
|
||||
//int read_count; /* number of blocks to read */
|
||||
|
||||
size_t logical_size;
|
||||
size_t physical_size;
|
||||
off_t physical_end;
|
||||
};
|
||||
|
||||
STREAMFILE* open_io_deblock_streamfile_f(STREAMFILE *sf, deblock_config_t *cfg);
|
||||
|
||||
#endif /* _DEBLOCK_STREAMFILE_H_ */
|
@ -1,241 +1,23 @@
|
||||
#ifndef _EA_EAAC_OPUS_STREAMFILE_H_
|
||||
#define _EA_EAAC_OPUS_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
typedef struct deblock_config_t deblock_config_t;
|
||||
typedef struct deblock_io_data deblock_io_data;
|
||||
|
||||
struct deblock_config_t {
|
||||
/* config (all optional) */
|
||||
size_t logical_size; /* pre-calculated size for performance (otherwise has to read the whole thing) */
|
||||
off_t stream_start; /* data start */
|
||||
size_t stream_size; /* data max */
|
||||
|
||||
size_t chunk_size; /* some size like a constant interleave */
|
||||
size_t skip_size; /* same */
|
||||
|
||||
int codec; /* codec or type variations */
|
||||
int channels;
|
||||
int big_endian;
|
||||
uint32_t config; /* some non-standard config value */
|
||||
|
||||
/* read=blocks from out stream to read) and "steps" (blocks from other streams to skip) */
|
||||
int step_start; /* initial step_count at stream start (often 0) */
|
||||
int step_count; /* number of blocks to step over from other streams */
|
||||
int read_count; /* number of blocks to read from this stream, after steps */
|
||||
|
||||
size_t track_size;
|
||||
int track_number;
|
||||
int track_count;
|
||||
size_t interleave_count;
|
||||
size_t interleave_last_count;
|
||||
|
||||
/* callback that setups deblock_io_data state, normally block_size and data_size */
|
||||
void (*block_callback)(STREAMFILE *sf, off_t offset, deblock_io_data *data);
|
||||
} ;
|
||||
|
||||
|
||||
struct deblock_io_data{
|
||||
/* initial config */
|
||||
deblock_config_t cfg;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake deblocked offset */
|
||||
off_t physical_offset; /* actual file offset */
|
||||
off_t block_size; /* current block (added to physical offset) */
|
||||
off_t skip_size; /* data to skip from block start to reach data (like a header) */
|
||||
off_t data_size; /* usable data in a block (added to logical offset) */
|
||||
//todo head/foot?
|
||||
int step_count; /* number of blocks to step over */
|
||||
int read_count; /* number of blocks to read */
|
||||
|
||||
size_t logical_size;
|
||||
size_t physical_size;
|
||||
off_t physical_end;
|
||||
} ;
|
||||
|
||||
|
||||
static void block_callback_default(STREAMFILE *sf, off_t offset, deblock_io_data *data) {
|
||||
data->block_size = data->cfg.chunk_size;
|
||||
data->skip_size = data->cfg.skip_size;
|
||||
data->data_size = data->block_size - data->skip_size;
|
||||
}
|
||||
|
||||
static size_t deblock_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, deblock_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
|
||||
/* re-start when previous offset (can't map logical<>physical offsets) */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
;VGM_LOG("DEBLOCK: restart offset=%lx + %x, po=%lx, lo=%lx\n", offset, length, data->physical_offset, data->logical_offset);
|
||||
data->physical_offset = data->cfg.stream_start;
|
||||
data->logical_offset = 0x00;
|
||||
data->block_size = 0;
|
||||
data->data_size = 0;
|
||||
data->skip_size = 0;
|
||||
|
||||
data->step_count = data->cfg.step_start;
|
||||
data->read_count = data->cfg.read_count;
|
||||
}
|
||||
|
||||
/* read blocks */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (offset < 0 ||
|
||||
(data->physical_offset >= data->cfg.stream_start + data->physical_size) ||
|
||||
(data->logical_size > 0 && offset > data->logical_size)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size <= 0) {
|
||||
data->cfg.block_callback(sf, offset, data);
|
||||
|
||||
if (data->block_size <= 0) {
|
||||
VGM_LOG("DEBLOCK: block size not set at %lx\n", data->physical_offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if 1
|
||||
if (data->step_count > 0) {
|
||||
data->step_count--;
|
||||
data->physical_offset += data->block_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
/* handle blocks from multiple streams */
|
||||
{
|
||||
if (data->step_count > 0) {
|
||||
data->step_count--;
|
||||
data->data_size = 0; /* step over this block */
|
||||
}
|
||||
else if (data->read_count) {//must detect when blocks has been read
|
||||
data->read_count--; /* read this block */
|
||||
|
||||
/* reset */
|
||||
if (data->step_count == 0 && data->read_count == 0) {
|
||||
data->step_count = data->cfg.step_count;
|
||||
data->read_count = data->cfg.read_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
|
||||
data->step_count = data->cfg.step_count;
|
||||
//VGM_LOG("ignore at %lx + %lx, skips=%i, reads=%i\n", data->physical_offset, data->block_size, data->step_count, data->read_count);
|
||||
continue;
|
||||
}
|
||||
|
||||
//VGM_LOG("accept at %lx + %lx, skips=%i, reads=%i\n", data->physical_offset, data->block_size, data->step_count, data->read_count);
|
||||
|
||||
/* read block data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, sf);
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t deblock_io_size(STREAMFILE *streamfile, deblock_io_data* data) {
|
||||
uint8_t buf[0x04];
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
if (data->cfg.logical_size) {
|
||||
data->logical_size = data->cfg.logical_size;
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
deblock_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* generic "de-blocker" helper for streams divided in blocks that have weird interleaves, their
|
||||
* decoder can't easily use blocked layout, or some other weird feature. Must pass a
|
||||
* deblock_config_t with setup and a callback that sets sizes of a single block. */
|
||||
static STREAMFILE* open_io_deblocker_streamfile_f(STREAMFILE *sf, deblock_config_t *cfg) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
deblock_io_data io_data = {0};
|
||||
|
||||
/* prepare data */
|
||||
io_data.cfg = *cfg; /* memcpy */
|
||||
|
||||
if (io_data.cfg.block_callback == NULL)
|
||||
io_data.cfg.block_callback = block_callback_default;
|
||||
|
||||
if (io_data.cfg.stream_start < 0)
|
||||
goto fail;
|
||||
if (io_data.cfg.step_start < 0 || io_data.cfg.step_count < 0)
|
||||
goto fail;
|
||||
|
||||
if (io_data.cfg.read_count == 0)
|
||||
io_data.cfg.read_count = 1;
|
||||
|
||||
io_data.physical_size = io_data.cfg.stream_size;
|
||||
if (io_data.physical_size > get_streamfile_size(sf) + io_data.cfg.stream_start || io_data.physical_size == 0)
|
||||
io_data.physical_size = get_streamfile_size(sf) - io_data.cfg.stream_start;
|
||||
io_data.physical_end = io_data.cfg.stream_start + io_data.physical_size;
|
||||
VGM_LOG("ps=%x, pe=%lx\n", io_data.physical_size, io_data.physical_end);
|
||||
io_data.logical_offset = -1; /* read reset */
|
||||
|
||||
//TODO: other validations
|
||||
|
||||
/* setup subfile */
|
||||
new_sf = open_io_streamfile_f(sf, &io_data, sizeof(deblock_io_data), deblock_io_read, deblock_io_size);
|
||||
return new_sf;
|
||||
fail:
|
||||
VGM_LOG("DEBLOCK: bad init\n");
|
||||
close_streamfile(sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*****************************************************/
|
||||
|
||||
static void block_callback(STREAMFILE *sf, off_t offset, deblock_io_data *data) {
|
||||
/* read the whole block, will be skipped for unwanted sub-streams */
|
||||
static void block_callback(STREAMFILE *sf, deblock_io_data *data) {
|
||||
data->block_size = 0x02 + read_u16be(data->physical_offset, sf);
|
||||
data->data_size = data->block_size;
|
||||
//VGM_LOG("read at %lx + %lx, skips=%i, reads=%i\n", data->physical_offset, data->block_size, data->step_count, data->read_count);
|
||||
}
|
||||
|
||||
static STREAMFILE* open_io_eaac_opus_streamfile_f(STREAMFILE *new_sf, int stream_number, int stream_count) {
|
||||
deblock_config_t cfg = {0};
|
||||
|
||||
cfg.step_start = stream_number;
|
||||
cfg.step_count = stream_count - 1;
|
||||
cfg.step_count = stream_count;
|
||||
cfg.block_callback = block_callback;
|
||||
/* starts from 0 since new_sf is pre-deblocked */
|
||||
|
||||
/* setup subfile */
|
||||
//new_sf = open_wrap_streamfile(sf); /* to be used with others */
|
||||
new_sf = open_io_deblocker_streamfile_f(new_sf, &cfg);
|
||||
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
|
@ -1,182 +1,45 @@
|
||||
#ifndef _EA_SCHL_STREAMFILE_H_
|
||||
#define _EA_SCHL_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* state */
|
||||
off_t logical_offset; /* offset that corresponds to physical_offset */
|
||||
off_t physical_offset; /* actual file offset */
|
||||
|
||||
/* config */
|
||||
int codec;
|
||||
int channels;
|
||||
off_t start_offset;
|
||||
size_t total_size; /* size of the resulting substream */
|
||||
} schl_io_data;
|
||||
|
||||
|
||||
/* Reads skipping EA's block headers, so the resulting data is smaller or larger than physical data.
|
||||
* physical/logical_offset should always be at the start of a block and only advance when a block is fully done */
|
||||
static size_t schl_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, schl_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
/* ignore bad reads */
|
||||
if (offset < 0 || offset > data->total_size) {
|
||||
return total_read;
|
||||
}
|
||||
|
||||
/* previous offset: re-start as we can't map logical<>physical offsets
|
||||
* (kinda slow as it trashes buffers, but shouldn't happen often) */
|
||||
if (offset < data->logical_offset) {
|
||||
data->physical_offset = data->start_offset;
|
||||
data->logical_offset = 0x00;
|
||||
}
|
||||
|
||||
/* read doing one EA block at a time */
|
||||
while (length > 0) {
|
||||
size_t to_read, bytes_read;
|
||||
off_t intrablock_offset, intradata_offset;
|
||||
uint32_t block_id, block_size, data_size, skip_size;
|
||||
|
||||
block_id = (uint32_t)read_32bitBE(data->physical_offset+0x00,streamfile);
|
||||
block_size = read_32bitLE(data->physical_offset+0x04,streamfile); /* always LE, hopefully */
|
||||
|
||||
if (block_id == 0x5343456C) /* "SCEl" */
|
||||
break; /* end block (no need to look for more SCHl for codecs needed this custom IO) */
|
||||
|
||||
if (block_id != 0x5343446C) { /* "SCDl" */
|
||||
data->physical_offset += block_size;
|
||||
continue; /* skip non-data blocks */
|
||||
}
|
||||
|
||||
switch(data->codec) {
|
||||
case 0x1b: /* ATRAC3plus */
|
||||
data_size = read_32bitLE(data->physical_offset+0x0c+0x04*data->channels,streamfile);
|
||||
skip_size = 0x0c+0x04*data->channels+0x04;
|
||||
break;
|
||||
default:
|
||||
return total_read;
|
||||
}
|
||||
|
||||
/* requested offset is outside current block, try next */
|
||||
if (offset >= data->logical_offset + data_size) {
|
||||
data->physical_offset += block_size;
|
||||
data->logical_offset += data_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* reads could fall in the middle of the block */
|
||||
intradata_offset = offset - data->logical_offset;
|
||||
intrablock_offset = skip_size + intradata_offset;
|
||||
|
||||
/* clamp reads up to this block's end */
|
||||
to_read = (data_size - intradata_offset);
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
if (to_read == 0)
|
||||
break; /* should never happen... */
|
||||
|
||||
/* finally read and move buffer/offsets */
|
||||
bytes_read = read_streamfile(dest, data->physical_offset + intrablock_offset, to_read, streamfile);
|
||||
total_read += bytes_read;
|
||||
if (bytes_read != to_read)
|
||||
break; /* couldn't read fully */
|
||||
|
||||
dest += bytes_read;
|
||||
offset += bytes_read;
|
||||
length -= bytes_read;
|
||||
|
||||
/* block fully read, go next */
|
||||
if (intradata_offset + bytes_read == data_size) {
|
||||
data->physical_offset += block_size;
|
||||
data->logical_offset += data_size;
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t schl_io_size(STREAMFILE *streamfile, schl_io_data* data) {
|
||||
off_t physical_offset, max_physical_offset;
|
||||
size_t total_size = 0;
|
||||
|
||||
if (data->total_size)
|
||||
return data->total_size;
|
||||
|
||||
physical_offset = data->start_offset;
|
||||
max_physical_offset = get_streamfile_size(streamfile);
|
||||
|
||||
/* get size of the underlying, non-blocked data */
|
||||
while (physical_offset < max_physical_offset) {
|
||||
uint32_t block_id, block_size, data_size;
|
||||
|
||||
block_id = (uint32_t)read_32bitBE(physical_offset+0x00,streamfile);
|
||||
block_size = read_32bitLE(physical_offset+0x04,streamfile); /* always LE, hopefully */
|
||||
|
||||
if (block_id == 0x5343456C) /* "SCEl" */
|
||||
break; /* end block (no need to look for more SCHl for codecs needed this custom IO) */
|
||||
|
||||
if (block_id != 0x5343446C) { /* "SCDl" */
|
||||
physical_offset += block_size;
|
||||
continue; /* skip non-data blocks */
|
||||
}
|
||||
|
||||
switch(data->codec) {
|
||||
case 0x1b: /* ATRAC3plus */
|
||||
data_size = read_32bitLE(physical_offset+0x0c+0x04*data->channels,streamfile);
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
physical_offset += block_size;
|
||||
total_size += data_size;
|
||||
}
|
||||
|
||||
|
||||
if (total_size > get_streamfile_size(streamfile)) {
|
||||
VGM_LOG("EA SCHL: wrong streamfile total_size\n");
|
||||
total_size = 0;
|
||||
}
|
||||
|
||||
data->total_size = total_size;
|
||||
return data->total_size;
|
||||
}
|
||||
|
||||
|
||||
/* Prepares custom IO for some blocked SCHl formats, that need clean reads without block headers.
|
||||
* Basically done to feed FFmpeg clean ATRAC3plus.
|
||||
*/
|
||||
static STREAMFILE* setup_schl_streamfile(STREAMFILE *streamFile, int codec, int channels, off_t start_offset, size_t total_size) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
schl_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(schl_io_data);
|
||||
|
||||
io_data.codec = codec;
|
||||
io_data.channels = channels;
|
||||
io_data.start_offset = start_offset;
|
||||
io_data.total_size = total_size; /* optional */
|
||||
io_data.physical_offset = start_offset;
|
||||
|
||||
/* 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, schl_io_read,schl_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_buffer_streamfile(new_streamFile,0);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _EA_SCHL_STREAMFILE_H_ */
|
||||
#ifndef _EA_SCHL_STREAMFILE_H_
|
||||
#define _EA_SCHL_STREAMFILE_H_
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
static void block_callback(STREAMFILE *sf, deblock_io_data *data) {
|
||||
uint32_t block_type, block_size;
|
||||
|
||||
block_type = read_u32be(data->physical_offset + 0x00, sf);
|
||||
block_size = read_u32le(data->physical_offset + 0x04, sf); /* always LE, hopefully */
|
||||
|
||||
if (block_type == 0x5343456C) /* "SCEl" end block */
|
||||
return;
|
||||
|
||||
data->block_size = block_size;
|
||||
if (block_type != 0x5343446C) /* skip non-"SCDl" blocks */
|
||||
return;
|
||||
|
||||
switch(data->cfg.codec) {
|
||||
case 0x1b: /* ATRAC3plus */
|
||||
data->data_size = read_32bitLE(data->physical_offset + 0x0c + 0x04 * data->cfg.channels, sf);
|
||||
data->skip_size = 0x0c + 0x04 * data->cfg.channels + 0x04;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Deblocks SCHl streams */
|
||||
static STREAMFILE* setup_schl_streamfile(STREAMFILE *sf, int codec, int channels, off_t stream_offset, size_t logical_size) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
deblock_config_t cfg = {0};
|
||||
|
||||
cfg.stream_start = stream_offset;
|
||||
cfg.logical_size = logical_size;
|
||||
cfg.codec = codec;
|
||||
cfg.channels = channels; //todo chunk size?
|
||||
cfg.block_callback = block_callback;
|
||||
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
|
||||
//new_sf = open_buffer_streamfile(new_sf, 0);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _EA_SCHL_STREAMFILE_H_ */
|
||||
|
@ -1,51 +1,36 @@
|
||||
#ifndef _JSTM_STREAMFILE_H_
|
||||
#define _JSTM_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
off_t start_offset;
|
||||
} jstm_decryption_data;
|
||||
|
||||
static size_t jstm_decryption_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, jstm_decryption_data* data) {
|
||||
size_t bytes_read;
|
||||
int i;
|
||||
|
||||
bytes_read = streamfile->read(streamfile, dest, offset, length);
|
||||
|
||||
/* decrypt data (xor) */
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
if (offset+i >= data->start_offset) {
|
||||
dest[i] = dest[i] ^ 0x5A;
|
||||
}
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
static STREAMFILE* setup_jstm_streamfile(STREAMFILE *streamFile, off_t start_offset) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
jstm_decryption_data io_data = {0};
|
||||
size_t io_data_size = sizeof(jstm_decryption_data);
|
||||
|
||||
/* setup decryption */
|
||||
io_data.start_offset = start_offset;
|
||||
|
||||
|
||||
/* setup custom streamfile */
|
||||
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, jstm_decryption_read,NULL);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _JSTM_STREAMFILE_H_ */
|
||||
#ifndef _JSTM_STREAMFILE_H_
|
||||
#define _JSTM_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
off_t start;
|
||||
} jstm_io_data;
|
||||
|
||||
static size_t jstm_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, jstm_io_data* data) {
|
||||
int i;
|
||||
size_t bytes = read_streamfile(dest, offset, length, sf);
|
||||
|
||||
/* decrypt data (xor) */
|
||||
for (i = 0; i < bytes; i++) {
|
||||
if (offset + i >= data->start) {
|
||||
dest[i] ^= 0x5A;
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/* decrypts JSTM stream */
|
||||
static STREAMFILE* setup_jstm_streamfile(STREAMFILE *sf, off_t start) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
jstm_io_data io_data = {0};
|
||||
|
||||
io_data.start = start;
|
||||
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(jstm_io_data), jstm_io_read, NULL);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _JSTM_STREAMFILE_H_ */
|
||||
|
@ -1,148 +1,21 @@
|
||||
#ifndef _KM9_STREAMFILE_H_
|
||||
#define _KM9_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
int stream_number;
|
||||
int stream_count;
|
||||
size_t interleave_size;
|
||||
off_t stream_offset;
|
||||
size_t stream_size;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* offset that corresponds to physical_offset */
|
||||
off_t physical_offset; /* actual file offset */
|
||||
|
||||
size_t skip_size; /* size to skip from a block start to reach data start */
|
||||
size_t data_size; /* logical size of the block */
|
||||
|
||||
size_t logical_size;
|
||||
} kma9_io_data;
|
||||
|
||||
|
||||
static size_t kma9_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, kma9_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
/* ignore bad reads */
|
||||
if (offset < 0 || offset > data->logical_size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* previous offset: re-start as we can't map logical<>physical offsets
|
||||
* (kinda slow as it trashes buffers, but shouldn't happen often) */
|
||||
if (offset < data->logical_offset) {
|
||||
data->logical_offset = 0x00;
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->data_size = 0;
|
||||
}
|
||||
|
||||
/* read blocks, one at a time */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (data->logical_offset >= data->logical_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
data->skip_size = data->interleave_size * data->stream_number;
|
||||
data->data_size = data->interleave_size;
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->interleave_size*data->stream_count;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
|
||||
offset += bytes_done;
|
||||
total_read += bytes_done;
|
||||
length -= bytes_done;
|
||||
dest += bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t kma9_io_size(STREAMFILE *streamfile, kma9_io_data* data) {
|
||||
off_t physical_offset = data->stream_offset;
|
||||
off_t max_physical_offset = get_streamfile_size(streamfile);
|
||||
size_t logical_size = 0;
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
/* get size of the logical stream */
|
||||
while (physical_offset < max_physical_offset) {
|
||||
logical_size += data->interleave_size;
|
||||
physical_offset += data->interleave_size*data->stream_count;
|
||||
}
|
||||
|
||||
if (logical_size > max_physical_offset)
|
||||
return 0;
|
||||
if (logical_size != data->stream_size)
|
||||
return 0;
|
||||
data->logical_size = logical_size;
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
|
||||
/* Prepares custom IO for KMA9, which interleaves ATRAC9 frames */
|
||||
static STREAMFILE* setup_kma9_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, size_t interleave_size, int stream_number, int stream_count) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
kma9_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(kma9_io_data);
|
||||
|
||||
io_data.stream_number = stream_number;
|
||||
io_data.stream_count = stream_count;
|
||||
io_data.stream_offset = stream_offset;
|
||||
io_data.stream_size = stream_size;
|
||||
io_data.interleave_size = interleave_size;
|
||||
io_data.physical_offset = stream_offset;
|
||||
io_data.logical_size = kma9_io_size(streamFile, &io_data); /* force init */
|
||||
|
||||
if (io_data.logical_size == 0) {
|
||||
VGM_LOG("KMA9: wrong logical size\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* 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, kma9_io_read,kma9_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#endif /* _KM9_STREAMFILE_H_ */
|
||||
#ifndef _KM9_STREAMFILE_H_
|
||||
#define _KM9_STREAMFILE_H_
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
/* Deinterleaves KMA9 streams */
|
||||
static STREAMFILE* setup_kma9_streamfile(STREAMFILE *sf, off_t stream_offset, size_t stream_size, size_t interleave_size, int stream_number, int stream_count) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
deblock_config_t cfg = {0};
|
||||
|
||||
cfg.stream_start = stream_offset;
|
||||
cfg.logical_size = stream_size;
|
||||
cfg.chunk_size = interleave_size;
|
||||
cfg.step_start = stream_number;
|
||||
cfg.step_count = stream_count;
|
||||
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _KM9_STREAMFILE_H_ */
|
||||
|
145
src/meta/lrmd.c
Normal file
145
src/meta/lrmd.c
Normal file
@ -0,0 +1,145 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "lrmd_streamfile.h"
|
||||
|
||||
/* LRMD - Sony/SCEI's format (Loco Roco Music Data?) [LocoRoco 2 (PSP), LocoRoco: Midnight Carnival (PSP)] */
|
||||
VGMSTREAM * init_vgmstream_lrmd(STREAMFILE *sf) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE * sf_h = NULL, *temp_sf = NULL;
|
||||
off_t stream_offset, section1_offset, section2_offset, basename_offset, subname_offset;
|
||||
size_t stream_size, layer_chunk;
|
||||
int loop_flag, channel_count, sample_rate, layers;
|
||||
int32_t num_samples, loop_start, loop_end;
|
||||
int total_subsongs, target_subsong = sf->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "lrmb"))
|
||||
goto fail;
|
||||
sf_h = open_streamfile_by_ext(sf, "lrmh");
|
||||
if (!sf_h) goto fail;
|
||||
|
||||
|
||||
if (read_u32be(0x00, sf_h) != 0x4C524D44) /* "LRMD" */
|
||||
goto fail;
|
||||
/* 0x00: version 1? */
|
||||
/* 0x08: header size */
|
||||
/* 0x0c: body size */
|
||||
|
||||
if (read_u32be(0x10, sf_h) != 0x52455144) /* "REQD" */
|
||||
goto fail;
|
||||
/* 0x14: chunk size */
|
||||
/* 0x18: null? */
|
||||
/* 0x1c: 1? */
|
||||
/* 0x20: null */
|
||||
basename_offset = read_u32le(0x24, sf_h);
|
||||
if (read_u16le(0x28, sf_h) != 0x4000) { /* pitch? */
|
||||
VGM_LOG("LRMD: unknown value\n");
|
||||
goto fail;
|
||||
}
|
||||
layer_chunk = read_u16le(0x2a, sf_h);
|
||||
num_samples = read_u32le(0x2c, sf_h);
|
||||
/* 0x30: null? */
|
||||
/* 0x34: data size for all layers */
|
||||
layers = read_u32le(0x38, sf_h);
|
||||
section1_offset = read_u32le(0x3c, sf_h);
|
||||
/* 0x40: seek/layer? table entries */
|
||||
/* 0x44: seek/layer? table offset */
|
||||
/* 0x48: section2 flag */
|
||||
section2_offset = read_u32le(0x4c, sf_h);
|
||||
/* 0x40: section3 flag */
|
||||
/* 0x44: section3 (unknown) */
|
||||
|
||||
total_subsongs = layers;
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
|
||||
/* data is divided into N interleaved layers sharing config, so it could be implemented as
|
||||
* layered, but since they have names it's worth showing as subsongs */
|
||||
|
||||
/* section1: layer config */
|
||||
section1_offset += (target_subsong - 1) * 0x18;
|
||||
/* 0x00: null */
|
||||
subname_offset = read_u32le(section1_offset + 0x04, sf_h);
|
||||
/* 0x08: unk */
|
||||
/* 0x0c: flags? */
|
||||
/* 0x10: null? */
|
||||
/* 0x14: null? */
|
||||
sample_rate = 44100;
|
||||
channel_count = 2;
|
||||
|
||||
/* section2: loops */
|
||||
/* 0x00: offset to "loop" name */
|
||||
if (section2_offset > 0) {
|
||||
loop_end = read_u32le(section2_offset + 0x04, sf_h);
|
||||
loop_start = read_u32le(section2_offset + 0x08, sf_h);
|
||||
loop_flag = read_u32le(section2_offset + 0x0c, sf_h);
|
||||
}
|
||||
else {
|
||||
loop_end = 0;
|
||||
loop_start = 0;
|
||||
loop_flag = 0;
|
||||
}
|
||||
|
||||
|
||||
//TODO: LR2's muihouse has buggy 7-layer interleave
|
||||
/* data de-interleave */
|
||||
temp_sf = setup_lrmd_streamfile(sf, layer_chunk / layers, (target_subsong-1), total_subsongs);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
stream_offset = 0x00;
|
||||
stream_size = get_streamfile_size(temp_sf);
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_LRMD;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
vgmstream->stream_size = stream_size;
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
int block_align, encoder_delay;
|
||||
|
||||
block_align = layer_chunk / layers;
|
||||
encoder_delay = 1024; /* assumed */
|
||||
vgmstream->num_samples -= encoder_delay;
|
||||
|
||||
vgmstream->codec_data = init_ffmpeg_atrac3_raw(temp_sf, stream_offset, stream_size, vgmstream->num_samples, vgmstream->channels, vgmstream->sample_rate, block_align, encoder_delay);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
/* name custom main + layer name */
|
||||
{
|
||||
int name_len = read_string(vgmstream->stream_name, STREAM_NAME_SIZE - 1, basename_offset, sf_h);
|
||||
|
||||
strcat(vgmstream->stream_name, "/");
|
||||
name_len++;
|
||||
|
||||
read_string(vgmstream->stream_name + name_len, STREAM_NAME_SIZE - name_len, subname_offset, sf_h);
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, temp_sf, stream_offset))
|
||||
goto fail;
|
||||
|
||||
close_streamfile(sf_h);
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(sf_h);
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
19
src/meta/lrmd_streamfile.h
Normal file
19
src/meta/lrmd_streamfile.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef _LRMD_STREAMFILE_H_
|
||||
#define _LRMD_STREAMFILE_H_
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
/* Deinterleaves LRMD streams */
|
||||
static STREAMFILE* setup_lrmd_streamfile(STREAMFILE *sf, size_t interleave_size, int stream_number, int stream_count) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
deblock_config_t cfg = {0};
|
||||
|
||||
cfg.chunk_size = interleave_size;
|
||||
cfg.step_start = stream_number;
|
||||
cfg.step_count = stream_count;
|
||||
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _LRMD_STREAMFILE_H_ */
|
@ -885,4 +885,6 @@ VGMSTREAM * init_vgmstream_tgc(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_kwb(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_lrmd(STREAMFILE* sf);
|
||||
|
||||
#endif /*_META_H*/
|
||||
|
@ -1,124 +1,27 @@
|
||||
#ifndef _MZRT_STREAMFILE_H_
|
||||
#define _MZRT_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
off_t stream_offset;
|
||||
size_t stream_size;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake offset */
|
||||
off_t physical_offset; /* actual offset */
|
||||
size_t block_size; /* current size */
|
||||
size_t skip_size; /* size from block start to reach data */
|
||||
size_t data_size; /* usable size in a block */
|
||||
|
||||
size_t logical_size;
|
||||
} mzrt_io_data;
|
||||
|
||||
|
||||
static size_t mzrt_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, mzrt_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
|
||||
/* re-start when previous offset (can't map logical<>physical offsets) */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
}
|
||||
|
||||
/* read blocks */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
/* 0x00: samples in this block */
|
||||
data->data_size = read_32bitBE(data->stream_offset + 0x04, streamfile);
|
||||
data->skip_size = 0x08;
|
||||
data->block_size = data->skip_size + data->data_size;
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t mzrt_io_size(STREAMFILE *streamfile, mzrt_io_data* data) {
|
||||
uint8_t buf[1];
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
mzrt_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* Handles deinterleaving of MZRT blocked streams */
|
||||
static STREAMFILE* setup_mzrt_streamfile(STREAMFILE *streamFile, off_t stream_offset) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
mzrt_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(mzrt_io_data);
|
||||
|
||||
io_data.stream_offset = stream_offset;
|
||||
io_data.stream_size = get_streamfile_size(streamFile) - stream_offset;
|
||||
io_data.logical_offset = -1; /* force phys offset reset */
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, mzrt_io_read,mzrt_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_buffer_streamfile(new_streamFile,0);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _MZRT_STREAMFILE_H_ */
|
||||
#ifndef _MZRT_STREAMFILE_H_
|
||||
#define _MZRT_STREAMFILE_H_
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
static void block_callback(STREAMFILE *sf, deblock_io_data *data) {
|
||||
/* 0x00: samples in this block */
|
||||
data->data_size = read_s32be(data->physical_offset + 0x04, sf);
|
||||
data->skip_size = 0x08;
|
||||
data->block_size = data->skip_size + data->data_size;
|
||||
}
|
||||
|
||||
/* Deblocks MZRT streams */
|
||||
static STREAMFILE* setup_mzrt_streamfile(STREAMFILE *sf, off_t stream_offset) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
deblock_config_t cfg = {0};
|
||||
|
||||
cfg.stream_start = stream_offset;
|
||||
cfg.block_callback = block_callback;
|
||||
|
||||
/* setup sf */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
|
||||
//new_sf = open_buffer_streamfile_f(new_sf, 0);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _MZRT_STREAMFILE_H_ */
|
||||
|
@ -1,174 +1,182 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
static int get_ogg_page_size(STREAMFILE *streamFile, off_t page_offset, off_t *out_data_offset, size_t *out_page_size);
|
||||
static int ogg_get_num_samples(STREAMFILE *streamFile, off_t start_offset);
|
||||
|
||||
/* Ogg Opus - standard Opus with optional looping comments [The Pillars of Earth (PC), Monster Boy and the Cursed Kingdom (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_ogg_opus(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset, data_offset = 0;
|
||||
size_t page_size = 0;
|
||||
int loop_flag, channel_count, original_rate;
|
||||
int loop_start = 0, loop_end = 0;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .opus: standard, .lopus: fake extension for plugins
|
||||
* .ogg: less common, .logg: same */
|
||||
if (!check_extensions(streamFile, "opus,lopus,ogg,logg"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4F676753) /* "OggS" */
|
||||
goto fail;
|
||||
/* see: https://tools.ietf.org/html/rfc7845.html */
|
||||
|
||||
start_offset = 0x00;
|
||||
|
||||
/* parse 1st page: opus head */
|
||||
if (!get_ogg_page_size(streamFile, start_offset, &data_offset, &page_size))
|
||||
goto fail;
|
||||
if (read_32bitBE(data_offset+0x00,streamFile) != 0x4F707573 && /* "Opus" */
|
||||
read_32bitBE(data_offset+0x04,streamFile) != 0x48656164) /* "Head" */
|
||||
goto fail;
|
||||
/* 0x01: version 1, fixed */
|
||||
channel_count = read_8bit(data_offset+0x09,streamFile);
|
||||
/* 0x0A: skip samples */
|
||||
original_rate = read_32bitLE(data_offset+0x0c,streamFile);
|
||||
/* 0x10: gain */
|
||||
/* 0x12: mapping family */
|
||||
|
||||
/* parse 2nd page: opus tags (also mandatory) */
|
||||
if (!get_ogg_page_size(streamFile, start_offset+page_size, &data_offset, &page_size))
|
||||
goto fail;
|
||||
if (read_32bitBE(data_offset+0x00,streamFile) != 0x4F707573 && /* "Opus" */
|
||||
read_32bitBE(data_offset+0x04,streamFile) != 0x54616773) /* "Tags" */
|
||||
goto fail;
|
||||
|
||||
loop_flag = 0;
|
||||
{
|
||||
char user_comment[1024+1];
|
||||
off_t offset;
|
||||
int vendor_size, comment_count, user_comment_size, user_comment_max;
|
||||
int i;
|
||||
int has_encoder_options = 0, has_title = 0;
|
||||
|
||||
vendor_size = read_32bitLE(data_offset+0x08,streamFile);
|
||||
comment_count = read_32bitLE(data_offset+0x0c+vendor_size,streamFile);
|
||||
|
||||
/* parse comments */
|
||||
offset = data_offset + 0x0c + vendor_size + 0x04;
|
||||
for (i = 0; i < comment_count; i++) {
|
||||
user_comment_size = read_32bitLE(offset+0x00,streamFile);
|
||||
user_comment_max = user_comment_size > 1024 ? 1024 : user_comment_size;
|
||||
read_string(user_comment,user_comment_max+1, offset+0x04,streamFile);
|
||||
|
||||
|
||||
/* parse loop strings */
|
||||
if (strstr(user_comment,"LOOP_START=")==user_comment) { /* Monster Boy and the Cursed Kingdom (Switch) */
|
||||
loop_start = atol(strrchr(user_comment,'=')+1);
|
||||
loop_flag = (loop_start >= 0);
|
||||
}
|
||||
else if (strstr(user_comment,"LOOP_END=")==user_comment) { /* LOOP_START pair */
|
||||
loop_end = atol(strrchr(user_comment,'=')+1);
|
||||
}
|
||||
else if (strstr(user_comment,"ENCODER_OPTIONS=")==user_comment) { /* for detection */
|
||||
has_encoder_options = 1;
|
||||
}
|
||||
else if (strstr(user_comment,"TITLE=")==user_comment) { /* for detection */
|
||||
has_title = 1;
|
||||
}
|
||||
|
||||
|
||||
//;VGM_LOG("OggOpus: user_comment=%s\n", user_comment);
|
||||
offset += 0x04 + user_comment_size;
|
||||
}
|
||||
|
||||
|
||||
/* Monster Boy has loop points for 44100hz (what), but Opus is resampled so
|
||||
* they must be adjusted (with extra checks just in case). */
|
||||
if (loop_flag && original_rate < 48000 && has_encoder_options && has_title) {
|
||||
float modifier = 48000.0f / (float)original_rate;
|
||||
loop_start = loop_start * modifier;
|
||||
loop_end = loop_end * modifier;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_OGG_OPUS;
|
||||
vgmstream->sample_rate = 48000; /* Opus always resamples to this */
|
||||
vgmstream->num_samples = ogg_get_num_samples(streamFile, 0x00);
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
vgmstream->codec_data = init_ffmpeg_offset(streamFile, start_offset, get_streamfile_size(streamFile));
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data);
|
||||
/* FFmpeg+libopus handles skip samples ok, FFmpeg+opus doesn't */
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* parse OggS's bizarre segment table */
|
||||
static int get_ogg_page_size(STREAMFILE *streamFile, off_t page_offset, off_t *out_data_offset, size_t *out_page_size) {
|
||||
uint8_t segments;
|
||||
size_t page_size = 0;
|
||||
int i;
|
||||
|
||||
if (read_32bitBE(page_offset+0x00,streamFile) != 0x4F676753) /* "OggS" */
|
||||
goto fail;
|
||||
|
||||
/* read all segment sizes */
|
||||
segments = (uint8_t)read_8bit(page_offset+0x1a, streamFile);
|
||||
for (i = 0; i < segments; i++) {
|
||||
page_size += (uint8_t)read_8bit(page_offset + 0x1b + i, streamFile);
|
||||
}
|
||||
page_size += 0x1b + segments;
|
||||
|
||||
if (out_data_offset) *out_data_offset = page_offset + 0x1b + segments;
|
||||
if (out_page_size) *out_page_size = page_size;
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Ogg doesn't have num_samples info, must manually seek+read last granule
|
||||
* (Xiph is insistent this is the One True Way). */
|
||||
static int ogg_get_num_samples(STREAMFILE *streamFile, off_t start_offset) {
|
||||
uint32_t expected_id = 0x4F676753;
|
||||
off_t offset = get_streamfile_size(streamFile) - 0x04-0x01-0x01-0x08-0x04-0x04-0x04;
|
||||
|
||||
//todo better buffer reads (Ogg page max is 0xFFFF)
|
||||
//lame way to force buffer, assuming it's around that
|
||||
read_32bitBE(offset - 0x4000, streamFile);
|
||||
|
||||
while (offset >= start_offset) {
|
||||
uint32_t current_id = read_32bitBE(offset, streamFile);
|
||||
if (current_id == expected_id) { /* if more checks are needed last page starts with 0x0004 */
|
||||
return read_32bitLE(offset+0x04+0x01+0x01, streamFile); /* get last granule = total samples (64b but whatevs) */
|
||||
}
|
||||
|
||||
offset--;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
static int get_ogg_page_size(STREAMFILE *streamFile, off_t page_offset, off_t *out_data_offset, size_t *out_page_size);
|
||||
static int ogg_get_num_samples(STREAMFILE *streamFile, off_t start_offset);
|
||||
|
||||
/* Ogg Opus - standard Opus with optional looping comments [The Pillars of Earth (PC), Monster Boy and the Cursed Kingdom (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_ogg_opus(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset, data_offset = 0;
|
||||
size_t page_size = 0;
|
||||
int loop_flag, channel_count, original_rate;
|
||||
int loop_start = 0, loop_end = 0;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .opus: standard, .lopus: fake extension for plugins
|
||||
* .ogg: less common, .logg: same
|
||||
* .bgm: Utawarerumono: Mask of Truth (PC) */
|
||||
if (!check_extensions(streamFile, "opus,lopus,ogg,logg,bgm"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4F676753) /* "OggS" */
|
||||
goto fail;
|
||||
/* see: https://tools.ietf.org/html/rfc7845.html */
|
||||
|
||||
start_offset = 0x00;
|
||||
|
||||
/* parse 1st page: opus head */
|
||||
if (!get_ogg_page_size(streamFile, start_offset, &data_offset, &page_size))
|
||||
goto fail;
|
||||
if (read_32bitBE(data_offset+0x00,streamFile) != 0x4F707573 && /* "Opus" */
|
||||
read_32bitBE(data_offset+0x04,streamFile) != 0x48656164) /* "Head" */
|
||||
goto fail;
|
||||
/* 0x01: version 1, fixed */
|
||||
channel_count = read_8bit(data_offset+0x09,streamFile);
|
||||
/* 0x0A: skip samples */
|
||||
original_rate = read_32bitLE(data_offset+0x0c,streamFile);
|
||||
/* 0x10: gain */
|
||||
/* 0x12: mapping family */
|
||||
|
||||
/* parse 2nd page: opus tags (also mandatory) */
|
||||
if (!get_ogg_page_size(streamFile, start_offset+page_size, &data_offset, &page_size))
|
||||
goto fail;
|
||||
if (read_32bitBE(data_offset+0x00,streamFile) != 0x4F707573 && /* "Opus" */
|
||||
read_32bitBE(data_offset+0x04,streamFile) != 0x54616773) /* "Tags" */
|
||||
goto fail;
|
||||
|
||||
loop_flag = 0;
|
||||
{
|
||||
char user_comment[1024+1];
|
||||
off_t offset;
|
||||
int vendor_size, comment_count, user_comment_size, user_comment_max;
|
||||
int i;
|
||||
int has_encoder_options = 0, has_title = 0;
|
||||
|
||||
vendor_size = read_32bitLE(data_offset+0x08,streamFile);
|
||||
comment_count = read_32bitLE(data_offset+0x0c+vendor_size,streamFile);
|
||||
|
||||
/* parse comments */
|
||||
offset = data_offset + 0x0c + vendor_size + 0x04;
|
||||
for (i = 0; i < comment_count; i++) {
|
||||
user_comment_size = read_32bitLE(offset+0x00,streamFile);
|
||||
user_comment_max = user_comment_size > 1024 ? 1024 : user_comment_size;
|
||||
read_string(user_comment,user_comment_max+1, offset+0x04,streamFile);
|
||||
|
||||
|
||||
/* parse loop strings */
|
||||
if (strstr(user_comment,"LOOP_START=")==user_comment) { /* Monster Boy and the Cursed Kingdom (Switch) */
|
||||
loop_start = atol(strrchr(user_comment,'=')+1);
|
||||
loop_flag = (loop_start >= 0);
|
||||
}
|
||||
else if (strstr(user_comment,"LOOP_END=")==user_comment) { /* LOOP_START pair */
|
||||
loop_end = atol(strrchr(user_comment,'=')+1);
|
||||
}
|
||||
else if (strstr(user_comment,"ENCODER_OPTIONS=")==user_comment) { /* for detection */
|
||||
has_encoder_options = 1;
|
||||
}
|
||||
else if (strstr(user_comment,"TITLE=")==user_comment) { /* for detection */
|
||||
has_title = 1;
|
||||
}
|
||||
else if (strstr(user_comment,"LoopStart=")==user_comment) { /* Utawarerumono: Mask of Truth (PC) */
|
||||
loop_start= atol(strrchr(user_comment,'=')+1);
|
||||
loop_flag = (loop_start >= 0);
|
||||
}
|
||||
else if (strstr(user_comment,"LoopEnd=")==user_comment) { /* LoopStart pair */
|
||||
loop_end = atol(strrchr(user_comment,'=')+1);
|
||||
}
|
||||
|
||||
|
||||
//;VGM_LOG("OggOpus: user_comment=%s\n", user_comment);
|
||||
offset += 0x04 + user_comment_size;
|
||||
}
|
||||
|
||||
|
||||
/* Monster Boy has loop points for 44100hz (what), but Opus is resampled so
|
||||
* they must be adjusted (with extra checks just in case). */
|
||||
if (loop_flag && original_rate < 48000 && has_encoder_options && has_title) {
|
||||
float modifier = 48000.0f / (float)original_rate;
|
||||
loop_start = loop_start * modifier;
|
||||
loop_end = loop_end * modifier;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_OGG_OPUS;
|
||||
vgmstream->sample_rate = 48000; /* Opus always resamples to this */
|
||||
vgmstream->num_samples = ogg_get_num_samples(streamFile, 0x00);
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
vgmstream->codec_data = init_ffmpeg_offset(streamFile, start_offset, get_streamfile_size(streamFile));
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data);
|
||||
/* FFmpeg+libopus handles skip samples ok, FFmpeg+opus doesn't */
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* parse OggS's bizarre segment table */
|
||||
static int get_ogg_page_size(STREAMFILE *streamFile, off_t page_offset, off_t *out_data_offset, size_t *out_page_size) {
|
||||
uint8_t segments;
|
||||
size_t page_size = 0;
|
||||
int i;
|
||||
|
||||
if (read_32bitBE(page_offset+0x00,streamFile) != 0x4F676753) /* "OggS" */
|
||||
goto fail;
|
||||
|
||||
/* read all segment sizes */
|
||||
segments = (uint8_t)read_8bit(page_offset+0x1a, streamFile);
|
||||
for (i = 0; i < segments; i++) {
|
||||
page_size += (uint8_t)read_8bit(page_offset + 0x1b + i, streamFile);
|
||||
}
|
||||
page_size += 0x1b + segments;
|
||||
|
||||
if (out_data_offset) *out_data_offset = page_offset + 0x1b + segments;
|
||||
if (out_page_size) *out_page_size = page_size;
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Ogg doesn't have num_samples info, must manually seek+read last granule
|
||||
* (Xiph is insistent this is the One True Way). */
|
||||
static int ogg_get_num_samples(STREAMFILE *streamFile, off_t start_offset) {
|
||||
uint32_t expected_id = 0x4F676753;
|
||||
off_t offset = get_streamfile_size(streamFile) - 0x04-0x01-0x01-0x08-0x04-0x04-0x04;
|
||||
|
||||
//todo better buffer reads (Ogg page max is 0xFFFF)
|
||||
//lame way to force buffer, assuming it's around that
|
||||
read_32bitBE(offset - 0x4000, streamFile);
|
||||
|
||||
while (offset >= start_offset) {
|
||||
uint32_t current_id = read_32bitBE(offset, streamFile);
|
||||
if (current_id == expected_id) { /* if more checks are needed last page starts with 0x0004 */
|
||||
return read_32bitLE(offset+0x04+0x01+0x01, streamFile); /* get last granule = total samples (64b but whatevs) */
|
||||
}
|
||||
|
||||
offset--;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,76 +1,59 @@
|
||||
#ifndef _OGG_VORBIS_STREAMFILE_H_
|
||||
#define _OGG_VORBIS_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
int is_encrypted;
|
||||
uint8_t key[0x100];
|
||||
size_t key_len;
|
||||
int is_nibble_swap;
|
||||
int is_header_swap;
|
||||
} ogg_vorbis_io_config_data;
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
ogg_vorbis_io_config_data cfg;
|
||||
} ogg_vorbis_io_data;
|
||||
|
||||
|
||||
static size_t ogg_vorbis_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, ogg_vorbis_io_data* data) {
|
||||
size_t bytes_read;
|
||||
int i;
|
||||
static const uint8_t header_swap[4] = { 0x4F,0x67,0x67,0x53 }; /* "OggS" */
|
||||
static const size_t header_size = 0x04;
|
||||
|
||||
bytes_read = streamfile->read(streamfile, dest, offset, length);
|
||||
|
||||
if (data->cfg.is_encrypted) {
|
||||
for (i = 0; i < bytes_read; i++) {
|
||||
if (data->cfg.is_header_swap && (offset + i) < header_size) {
|
||||
dest[i] = header_swap[(offset + i) % header_size];
|
||||
}
|
||||
else {
|
||||
if (!data->cfg.key_len && !data->cfg.is_nibble_swap)
|
||||
break;
|
||||
if (data->cfg.key_len)
|
||||
dest[i] ^= data->cfg.key[(offset + i) % data->cfg.key_len];
|
||||
if (data->cfg.is_nibble_swap)
|
||||
dest[i] = ((dest[i] << 4) & 0xf0) | ((dest[i] >> 4) & 0x0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
//todo maybe use generic decryption streamfile
|
||||
static STREAMFILE* setup_ogg_vorbis_streamfile(STREAMFILE *streamFile, ogg_vorbis_io_config_data cfg) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
ogg_vorbis_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(ogg_vorbis_io_data);
|
||||
|
||||
/* setup decryption */
|
||||
io_data.cfg = cfg; /* memcpy */
|
||||
|
||||
|
||||
/* setup custom streamfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
//todo extension .ogg?
|
||||
|
||||
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, ogg_vorbis_io_read,NULL);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#endif /* _OGG_VORBIS_STREAMFILE_H_ */
|
||||
#ifndef _OGG_VORBIS_STREAMFILE_H_
|
||||
#define _OGG_VORBIS_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
int is_encrypted;
|
||||
uint8_t key[0x100];
|
||||
size_t key_len;
|
||||
int is_nibble_swap;
|
||||
int is_header_swap;
|
||||
} ogg_vorbis_io_config_data;
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
ogg_vorbis_io_config_data cfg;
|
||||
} ogg_vorbis_io_data;
|
||||
|
||||
|
||||
static size_t ogg_vorbis_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, ogg_vorbis_io_data* data) {
|
||||
static const uint8_t header_swap[4] = { 0x4F,0x67,0x67,0x53 }; /* "OggS" */
|
||||
static const size_t header_size = 0x04;
|
||||
int i;
|
||||
size_t bytes = read_streamfile(dest, offset, length, sf);
|
||||
|
||||
if (data->cfg.is_encrypted) {
|
||||
for (i = 0; i < bytes; i++) {
|
||||
if (data->cfg.is_header_swap && (offset + i) < header_size) {
|
||||
dest[i] = header_swap[(offset + i) % header_size];
|
||||
}
|
||||
else {
|
||||
if (!data->cfg.key_len && !data->cfg.is_nibble_swap)
|
||||
break;
|
||||
if (data->cfg.key_len)
|
||||
dest[i] ^= data->cfg.key[(offset + i) % data->cfg.key_len];
|
||||
if (data->cfg.is_nibble_swap)
|
||||
dest[i] = ((dest[i] << 4) & 0xf0) | ((dest[i] >> 4) & 0x0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
//todo maybe use generic decryption streamfile
|
||||
/* Decrypts Ogg Vorbis streams */
|
||||
static STREAMFILE* setup_ogg_vorbis_streamfile(STREAMFILE *sf, ogg_vorbis_io_config_data cfg) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
ogg_vorbis_io_data io_data = {0};
|
||||
|
||||
io_data.cfg = cfg; /* memcpy */
|
||||
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(ogg_vorbis_io_data), ogg_vorbis_io_read, NULL);
|
||||
//new_sf = open_fakename_streamfile_f(new_sf, NULL, "ogg"); //todo?
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _OGG_VORBIS_STREAMFILE_H_ */
|
||||
|
893
src/meta/opus.c
893
src/meta/opus.c
@ -1,439 +1,454 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "opus_interleave_streamfile.h"
|
||||
|
||||
/* Nintendo OPUS - from Switch games, including header variations (not the same as Ogg Opus) */
|
||||
|
||||
static VGMSTREAM * init_vgmstream_opus(STREAMFILE *streamFile, meta_t meta_type, off_t offset, int32_t num_samples, int32_t loop_start, int32_t loop_end) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag = 0, channel_count;
|
||||
off_t data_offset, multichannel_offset = 0;
|
||||
size_t data_size, skip = 0;
|
||||
|
||||
|
||||
if ((uint32_t)read_32bitLE(offset + 0x00,streamFile) != 0x80000001)
|
||||
goto fail;
|
||||
|
||||
channel_count = read_8bit(offset + 0x09, streamFile);
|
||||
/* 0x0a: packet size if CBR, 0 if VBR */
|
||||
data_offset = offset + read_32bitLE(offset + 0x10, streamFile);
|
||||
skip = read_16bitLE(offset + 0x1c, streamFile);
|
||||
/* 0x1e: ? (seen in Lego Movie 2 (Switch)) */
|
||||
|
||||
/* recent >2ch info [Clannad (Switch)] */
|
||||
if ((uint32_t)read_32bitLE(offset + 0x20, streamFile) == 0x80000005) {
|
||||
multichannel_offset = offset + 0x20;
|
||||
}
|
||||
|
||||
if ((uint32_t)read_32bitLE(data_offset, streamFile) != 0x80000004)
|
||||
goto fail;
|
||||
|
||||
data_size = read_32bitLE(data_offset + 0x04, streamFile);
|
||||
|
||||
start_offset = data_offset + 0x08;
|
||||
loop_flag = (loop_end > 0); /* -1 when not set */
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_type;
|
||||
vgmstream->sample_rate = read_32bitLE(offset + 0x0c,streamFile);
|
||||
if (vgmstream->sample_rate == 16000)
|
||||
vgmstream->sample_rate = 48000; // Grandia HD Collection contains a false sample_rate in header
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
vgmstream->stream_size = data_size; /* to avoid inflated sizes from fake OggS IO */
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
opus_config cfg = {0};
|
||||
|
||||
cfg.channels = vgmstream->channels;
|
||||
cfg.skip = skip;
|
||||
cfg.sample_rate = vgmstream->sample_rate;
|
||||
|
||||
if (multichannel_offset && vgmstream->channels <= 8) {
|
||||
int i;
|
||||
cfg.stream_count = read_8bit(multichannel_offset + 0x08,streamFile);
|
||||
cfg.coupled_count = read_8bit(multichannel_offset + 0x09,streamFile);
|
||||
for (i = 0; i < vgmstream->channels; i++) {
|
||||
cfg.channel_mapping[i] = read_8bit(multichannel_offset + 0x0a + i,streamFile);
|
||||
}
|
||||
}
|
||||
|
||||
vgmstream->codec_data = init_ffmpeg_switch_opus_config(streamFile, start_offset,data_size, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data);
|
||||
|
||||
if (vgmstream->num_samples == 0) {
|
||||
vgmstream->num_samples = switch_opus_get_samples(start_offset, data_size, streamFile) - skip;
|
||||
}
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* standard Switch Opus, Nintendo header + raw data (generated by opus_test.c?) [Lego City Undercover (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_std(STREAMFILE *streamFile) {
|
||||
STREAMFILE * PSIFile = NULL;
|
||||
off_t offset;
|
||||
int num_samples, loop_start, loop_end;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile,"opus,lopus"))
|
||||
goto fail;
|
||||
|
||||
offset = 0x00;
|
||||
|
||||
/* BlazBlue: Cross Tag Battle (Switch) PSI Metadata for corresponding Opus */
|
||||
/* Maybe future Arc System Works games will use this too? */
|
||||
PSIFile = open_streamfile_by_ext(streamFile, "psi");
|
||||
if (PSIFile) {
|
||||
num_samples = read_32bitLE(0x8C, PSIFile);
|
||||
loop_start = read_32bitLE(0x84, PSIFile);
|
||||
loop_end = read_32bitLE(0x88, PSIFile);
|
||||
close_streamfile(PSIFile);
|
||||
}
|
||||
else {
|
||||
num_samples = 0;
|
||||
loop_start = 0;
|
||||
loop_end = 0;
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Nippon1 variation [Disgaea 5 (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_n1(STREAMFILE *streamFile) {
|
||||
off_t offset;
|
||||
int num_samples, loop_start, loop_end;
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(streamFile,"opus,lopus"))
|
||||
goto fail;
|
||||
if (!((read_32bitBE(0x04,streamFile) == 0x00000000 && read_32bitBE(0x0c,streamFile) == 0x00000000) ||
|
||||
(read_32bitBE(0x04,streamFile) == 0xFFFFFFFF && read_32bitBE(0x0c,streamFile) == 0xFFFFFFFF)))
|
||||
goto fail;
|
||||
|
||||
offset = 0x10;
|
||||
num_samples = 0;
|
||||
loop_start = read_32bitLE(0x00,streamFile);
|
||||
loop_end = read_32bitLE(0x08,streamFile);
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Capcom variation [Ultra Street Fighter II (Switch), Resident Evil: Revelations (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_capcom(STREAMFILE *streamFile) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
off_t offset;
|
||||
int num_samples, loop_start, loop_end;
|
||||
int channel_count;
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(streamFile,"opus,lopus"))
|
||||
goto fail;
|
||||
|
||||
channel_count = read_32bitLE(0x04,streamFile);
|
||||
if (channel_count != 1 && channel_count != 2 && channel_count != 6)
|
||||
goto fail; /* unknown stream layout */
|
||||
|
||||
num_samples = read_32bitLE(0x00,streamFile);
|
||||
/* 0x04: channels, >2 uses interleaved streams (2ch+2ch+2ch) */
|
||||
loop_start = read_32bitLE(0x08,streamFile);
|
||||
loop_end = read_32bitLE(0x0c,streamFile);
|
||||
/* 0x10: frame size (with extra data) */
|
||||
/* 0x14: extra chunk count */
|
||||
/* 0x18: null */
|
||||
offset = read_32bitLE(0x1c,streamFile);
|
||||
/* 0x20-8: config? (0x0077C102 04000000 E107070C) */
|
||||
/* 0x2c: some size? */
|
||||
/* 0x30+: extra chunks (0x00: 0x7f, 0x04: num_sample), alt loop starts/regions? */
|
||||
|
||||
if (channel_count == 6) {
|
||||
/* 2ch multistream hacky-hacks, don't try this at home. We'll end up with:
|
||||
* main vgmstream > N vgmstream layers > substream IO deinterleaver > opus meta > Opus IO transmogrifier (phew) */
|
||||
layered_layout_data* data = NULL;
|
||||
int layers = channel_count / 2;
|
||||
int i;
|
||||
int loop_flag = (loop_end > 0);
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->layout_type = layout_layered;
|
||||
|
||||
/* init layout */
|
||||
data = init_layout_layered(layers);
|
||||
if (!data) goto fail;
|
||||
vgmstream->layout_data = data;
|
||||
|
||||
/* open each layer subfile */
|
||||
for (i = 0; i < layers; i++) {
|
||||
STREAMFILE* temp_streamFile = setup_opus_interleave_streamfile(streamFile, offset+0x28*i, layers);
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
data->layers[i] = init_vgmstream_opus(temp_streamFile, meta_OPUS, 0x00, num_samples,loop_start,loop_end);
|
||||
close_streamfile(temp_streamFile);
|
||||
if (!data->layers[i]) goto fail;
|
||||
}
|
||||
|
||||
/* setup layered VGMSTREAMs */
|
||||
if (!setup_layout_layered(data))
|
||||
goto fail;
|
||||
|
||||
vgmstream->sample_rate = data->layers[0]->sample_rate;
|
||||
vgmstream->num_samples = data->layers[0]->num_samples;
|
||||
vgmstream->loop_start_sample = data->layers[0]->loop_start_sample;
|
||||
vgmstream->loop_end_sample = data->layers[0]->loop_end_sample;
|
||||
vgmstream->meta_type = meta_OPUS;
|
||||
vgmstream->coding_type = data->layers[0]->coding_type;
|
||||
|
||||
return vgmstream;
|
||||
}
|
||||
else {
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end);
|
||||
}
|
||||
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Procyon Studio variation [Xenoblade Chronicles 2 (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_nop(STREAMFILE *streamFile) {
|
||||
off_t offset;
|
||||
int num_samples, loop_start = 0, loop_end = 0, loop_flag;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile,"nop"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, streamFile) != 0x73616466 || /* "sadf" */
|
||||
read_32bitBE(0x08, streamFile) != 0x6f707573) /* "opus" */
|
||||
goto fail;
|
||||
|
||||
offset = read_32bitLE(0x1c, streamFile);
|
||||
num_samples = read_32bitLE(0x28, streamFile);
|
||||
loop_flag = read_8bit(0x19, streamFile);
|
||||
if (loop_flag) {
|
||||
loop_start = read_32bitLE(0x2c, streamFile);
|
||||
loop_end = read_32bitLE(0x30, streamFile);
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Shin'en variation [Fast RMX (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_shinen(STREAMFILE *streamFile) {
|
||||
off_t offset = 0;
|
||||
int num_samples, loop_start, loop_end;
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(streamFile,"opus,lopus"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x08,streamFile) != 0x01000080)
|
||||
goto fail;
|
||||
|
||||
offset = 0x08;
|
||||
num_samples = 0;
|
||||
loop_start = read_32bitLE(0x00,streamFile);
|
||||
loop_end = read_32bitLE(0x04,streamFile); /* 0 if no loop */
|
||||
|
||||
if (loop_start > loop_end)
|
||||
goto fail; /* just in case */
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Bandai Namco Opus (found in NUS3Banks) [Taiko no Tatsujin: Nintendo Switch Version!] */
|
||||
VGMSTREAM * init_vgmstream_opus_nus3(STREAMFILE *streamFile) {
|
||||
off_t offset = 0;
|
||||
int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag;
|
||||
|
||||
/* checks */
|
||||
/* .opus: header ID (they only exist inside .nus3bank) */
|
||||
if (!check_extensions(streamFile, "opus,lopus"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, streamFile) != 0x4F505553) /* "OPUS" */
|
||||
goto fail;
|
||||
|
||||
/* Here's an interesting quirk, OPUS header contains big endian values
|
||||
while the Nintendo Opus header and data that follows remain little endian as usual */
|
||||
offset = read_32bitBE(0x20, streamFile);
|
||||
num_samples = read_32bitBE(0x08, streamFile);
|
||||
|
||||
/* Check if there's a loop end value to determine loop_flag*/
|
||||
loop_flag = read_32bitBE(0x18, streamFile);
|
||||
if (loop_flag) {
|
||||
loop_start = read_32bitBE(0x14, streamFile);
|
||||
loop_end = read_32bitBE(0x18, streamFile);
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Nippon Ichi SPS wrapper (non-segmented) [Ys VIII: Lacrimosa of Dana (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_sps_n1(STREAMFILE *streamFile) {
|
||||
off_t offset;
|
||||
int num_samples, loop_start = 0, loop_end = 0, loop_flag;
|
||||
|
||||
/* checks */
|
||||
/* .sps: Labyrinth of Refrain - Coven of Dusk (Switch)
|
||||
* .nlsd: Disgaea Refine (Switch), Ys VIII (Switch) */
|
||||
if (!check_extensions(streamFile, "sps,nlsd"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, streamFile) != 0x09000000) /* file type (see other N1 SPS) */
|
||||
goto fail;
|
||||
|
||||
offset = 0x1C;
|
||||
num_samples = read_32bitLE(0x0C, streamFile);
|
||||
|
||||
/* sections num_samples (remnant of segmented opus_sps_n1):
|
||||
* 0x10: intro, 0x14: loop, 0x18: end (all must add up to num_samples) */
|
||||
loop_flag = read_32bitLE(0x18, streamFile); /* with loop disabled only loop section has samples */
|
||||
if (loop_flag) {
|
||||
loop_start = read_32bitLE(0x10, streamFile);
|
||||
loop_end = loop_start + read_32bitLE(0x14, streamFile);
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* AQUASTYLE wrapper [Touhou Genso Wanderer -Reloaded- (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_opusx(STREAMFILE *streamFile) {
|
||||
off_t offset;
|
||||
int num_samples, loop_start = 0, loop_end = 0;
|
||||
float modifier;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "opusx"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, streamFile) != 0x4F505553) /* "OPUS" */
|
||||
goto fail;
|
||||
|
||||
offset = 0x10;
|
||||
/* values are for the original 44100 files, but Opus resamples to 48000 */
|
||||
modifier = 48000.0f / 44100.0f;
|
||||
num_samples = 0;//read_32bitLE(0x04, streamFile) * modifier; /* better use calc'd num_samples */
|
||||
loop_start = read_32bitLE(0x08, streamFile) * modifier;
|
||||
loop_end = read_32bitLE(0x0c, streamFile) * modifier;
|
||||
|
||||
/* resampling calcs are slighly off and may to over num_samples, but by removing delay seems ok */
|
||||
if (loop_start >= 120) {
|
||||
loop_start -= 128;
|
||||
loop_end -= 128;
|
||||
}
|
||||
else {
|
||||
loop_end = 0;
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Prototype variation [Clannad (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_prototype(STREAMFILE *streamFile) {
|
||||
off_t offset = 0;
|
||||
int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "opus,lopus"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, streamFile) != 0x4F505553 || /* "OPUS" */
|
||||
read_32bitBE(0x18, streamFile) != 0x01000080)
|
||||
goto fail;
|
||||
|
||||
offset = 0x18;
|
||||
num_samples = read_32bitLE(0x08, streamFile);
|
||||
|
||||
/* Check if there's a loop end value to determine loop_flag*/
|
||||
loop_flag = read_32bitLE(0x10, streamFile);
|
||||
if (loop_flag) {
|
||||
loop_start = read_32bitLE(0x0C, streamFile);
|
||||
loop_end = read_32bitLE(0x10, streamFile);
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Edelweiss variation [Astebreed (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_opusnx(STREAMFILE *streamFile) {
|
||||
off_t offset = 0;
|
||||
int num_samples = 0, loop_start = 0, loop_end = 0;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "opus,lopus"))
|
||||
goto fail;
|
||||
if (read_64bitBE(0x00, streamFile) != 0x4F5055534E580000) /* "OPUSNX\0\0" */
|
||||
goto fail;
|
||||
|
||||
offset = 0x10;
|
||||
num_samples = 0; //read_32bitLE(0x08, streamFile); /* samples with encoder delay */
|
||||
if (read_32bitLE(0x0c, streamFile) != 0)
|
||||
goto fail;
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Square Enix variation [Dragon Quest I-III (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_sqex(STREAMFILE *streamFile) {
|
||||
off_t offset = 0;
|
||||
int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "opus,lopus"))
|
||||
goto fail;
|
||||
if (read_64bitBE(0x00, streamFile) != 0x0100000002000000)
|
||||
goto fail;
|
||||
|
||||
offset = read_32bitLE(0x0C, streamFile);
|
||||
num_samples = read_32bitLE(0x1C, streamFile);
|
||||
|
||||
/* Check if there's a loop end value to determine loop_flag*/
|
||||
loop_flag = read_32bitLE(0x18, streamFile);
|
||||
if (loop_flag) {
|
||||
loop_start = read_32bitLE(0x14, streamFile);
|
||||
loop_end = read_32bitLE(0x18, streamFile);
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "opus_interleave_streamfile.h"
|
||||
|
||||
/* Nintendo OPUS - from Switch games, including header variations (not the same as Ogg Opus) */
|
||||
|
||||
static VGMSTREAM * init_vgmstream_opus(STREAMFILE *streamFile, meta_t meta_type, off_t offset, int32_t num_samples, int32_t loop_start, int32_t loop_end) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag = 0, channel_count;
|
||||
off_t data_offset, multichannel_offset = 0;
|
||||
size_t data_size, skip = 0;
|
||||
|
||||
|
||||
if ((uint32_t)read_32bitLE(offset + 0x00,streamFile) != 0x80000001)
|
||||
goto fail;
|
||||
|
||||
channel_count = read_8bit(offset + 0x09, streamFile);
|
||||
/* 0x0a: packet size if CBR, 0 if VBR */
|
||||
data_offset = offset + read_32bitLE(offset + 0x10, streamFile);
|
||||
skip = read_16bitLE(offset + 0x1c, streamFile);
|
||||
/* 0x1e: ? (seen in Lego Movie 2 (Switch)) */
|
||||
|
||||
/* recent >2ch info [Clannad (Switch)] */
|
||||
if ((uint32_t)read_32bitLE(offset + 0x20, streamFile) == 0x80000005) {
|
||||
multichannel_offset = offset + 0x20;
|
||||
}
|
||||
|
||||
if ((uint32_t)read_32bitLE(data_offset, streamFile) != 0x80000004)
|
||||
goto fail;
|
||||
|
||||
data_size = read_32bitLE(data_offset + 0x04, streamFile);
|
||||
|
||||
start_offset = data_offset + 0x08;
|
||||
loop_flag = (loop_end > 0); /* -1 when not set */
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_type;
|
||||
vgmstream->sample_rate = read_32bitLE(offset + 0x0c,streamFile);
|
||||
if (vgmstream->sample_rate == 16000)
|
||||
vgmstream->sample_rate = 48000; // Grandia HD Collection contains a false sample_rate in header
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
vgmstream->stream_size = data_size; /* to avoid inflated sizes from fake OggS IO */
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
opus_config cfg = {0};
|
||||
|
||||
cfg.channels = vgmstream->channels;
|
||||
cfg.skip = skip;
|
||||
cfg.sample_rate = vgmstream->sample_rate;
|
||||
|
||||
if (multichannel_offset && vgmstream->channels <= 8) {
|
||||
int i;
|
||||
cfg.stream_count = read_8bit(multichannel_offset + 0x08,streamFile);
|
||||
cfg.coupled_count = read_8bit(multichannel_offset + 0x09,streamFile);
|
||||
for (i = 0; i < vgmstream->channels; i++) {
|
||||
cfg.channel_mapping[i] = read_8bit(multichannel_offset + 0x0a + i,streamFile);
|
||||
}
|
||||
}
|
||||
|
||||
vgmstream->codec_data = init_ffmpeg_switch_opus_config(streamFile, start_offset,data_size, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data);
|
||||
|
||||
if (vgmstream->num_samples == 0) {
|
||||
vgmstream->num_samples = switch_opus_get_samples(start_offset, data_size, streamFile) - skip;
|
||||
}
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* standard Switch Opus, Nintendo header + raw data (generated by opus_test.c?) [Lego City Undercover (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_std(STREAMFILE *streamFile) {
|
||||
STREAMFILE * PSIFile = NULL;
|
||||
off_t offset;
|
||||
int num_samples, loop_start, loop_end;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile,"opus,lopus"))
|
||||
goto fail;
|
||||
|
||||
offset = 0x00;
|
||||
|
||||
/* BlazBlue: Cross Tag Battle (Switch) PSI Metadata for corresponding Opus */
|
||||
/* Maybe future Arc System Works games will use this too? */
|
||||
PSIFile = open_streamfile_by_ext(streamFile, "psi");
|
||||
if (PSIFile) {
|
||||
num_samples = read_32bitLE(0x8C, PSIFile);
|
||||
loop_start = read_32bitLE(0x84, PSIFile);
|
||||
loop_end = read_32bitLE(0x88, PSIFile);
|
||||
close_streamfile(PSIFile);
|
||||
}
|
||||
else {
|
||||
num_samples = 0;
|
||||
loop_start = 0;
|
||||
loop_end = 0;
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Nippon1 variation [Disgaea 5 (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_n1(STREAMFILE *streamFile) {
|
||||
off_t offset;
|
||||
int num_samples, loop_start, loop_end;
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(streamFile,"opus,lopus"))
|
||||
goto fail;
|
||||
if (!((read_32bitBE(0x04,streamFile) == 0x00000000 && read_32bitBE(0x0c,streamFile) == 0x00000000) ||
|
||||
(read_32bitBE(0x04,streamFile) == 0xFFFFFFFF && read_32bitBE(0x0c,streamFile) == 0xFFFFFFFF)))
|
||||
goto fail;
|
||||
|
||||
offset = 0x10;
|
||||
num_samples = 0;
|
||||
loop_start = read_32bitLE(0x00,streamFile);
|
||||
loop_end = read_32bitLE(0x08,streamFile);
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Capcom variation [Ultra Street Fighter II (Switch), Resident Evil: Revelations (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_capcom(STREAMFILE *streamFile) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
off_t offset;
|
||||
int num_samples, loop_start, loop_end;
|
||||
int channel_count;
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(streamFile,"opus,lopus"))
|
||||
goto fail;
|
||||
|
||||
channel_count = read_32bitLE(0x04,streamFile);
|
||||
if (channel_count != 1 && channel_count != 2 && channel_count != 6)
|
||||
goto fail; /* unknown stream layout */
|
||||
|
||||
num_samples = read_32bitLE(0x00,streamFile);
|
||||
/* 0x04: channels, >2 uses interleaved streams (2ch+2ch+2ch) */
|
||||
loop_start = read_32bitLE(0x08,streamFile);
|
||||
loop_end = read_32bitLE(0x0c,streamFile);
|
||||
/* 0x10: frame size (with extra data) */
|
||||
/* 0x14: extra chunk count */
|
||||
/* 0x18: null */
|
||||
offset = read_32bitLE(0x1c,streamFile);
|
||||
/* 0x20-8: config? (0x0077C102 04000000 E107070C) */
|
||||
/* 0x2c: some size? */
|
||||
/* 0x30+: extra chunks (0x00: 0x7f, 0x04: num_sample), alt loop starts/regions? */
|
||||
|
||||
if (channel_count == 6) {
|
||||
/* 2ch multistream hacky-hacks, don't try this at home. We'll end up with:
|
||||
* main vgmstream > N vgmstream layers > substream IO deinterleaver > opus meta > Opus IO transmogrifier (phew) */
|
||||
layered_layout_data* data = NULL;
|
||||
int layers = channel_count / 2;
|
||||
int i;
|
||||
int loop_flag = (loop_end > 0);
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->layout_type = layout_layered;
|
||||
|
||||
/* init layout */
|
||||
data = init_layout_layered(layers);
|
||||
if (!data) goto fail;
|
||||
vgmstream->layout_data = data;
|
||||
|
||||
/* open each layer subfile */
|
||||
for (i = 0; i < layers; i++) {
|
||||
STREAMFILE* temp_streamFile = setup_opus_interleave_streamfile(streamFile, offset+0x28*i, layers);
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
data->layers[i] = init_vgmstream_opus(temp_streamFile, meta_OPUS, 0x00, num_samples,loop_start,loop_end);
|
||||
close_streamfile(temp_streamFile);
|
||||
if (!data->layers[i]) goto fail;
|
||||
}
|
||||
|
||||
/* setup layered VGMSTREAMs */
|
||||
if (!setup_layout_layered(data))
|
||||
goto fail;
|
||||
|
||||
vgmstream->sample_rate = data->layers[0]->sample_rate;
|
||||
vgmstream->num_samples = data->layers[0]->num_samples;
|
||||
vgmstream->loop_start_sample = data->layers[0]->loop_start_sample;
|
||||
vgmstream->loop_end_sample = data->layers[0]->loop_end_sample;
|
||||
vgmstream->meta_type = meta_OPUS;
|
||||
vgmstream->coding_type = data->layers[0]->coding_type;
|
||||
|
||||
return vgmstream;
|
||||
}
|
||||
else {
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end);
|
||||
}
|
||||
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Procyon Studio variation [Xenoblade Chronicles 2 (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_nop(STREAMFILE *streamFile) {
|
||||
off_t offset;
|
||||
int num_samples, loop_start = 0, loop_end = 0, loop_flag;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile,"nop"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, streamFile) != 0x73616466 || /* "sadf" */
|
||||
read_32bitBE(0x08, streamFile) != 0x6f707573) /* "opus" */
|
||||
goto fail;
|
||||
|
||||
offset = read_32bitLE(0x1c, streamFile);
|
||||
num_samples = read_32bitLE(0x28, streamFile);
|
||||
loop_flag = read_8bit(0x19, streamFile);
|
||||
if (loop_flag) {
|
||||
loop_start = read_32bitLE(0x2c, streamFile);
|
||||
loop_end = read_32bitLE(0x30, streamFile);
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Shin'en variation [Fast RMX (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_shinen(STREAMFILE *streamFile) {
|
||||
off_t offset = 0;
|
||||
int num_samples, loop_start, loop_end;
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(streamFile,"opus,lopus"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x08,streamFile) != 0x01000080)
|
||||
goto fail;
|
||||
|
||||
offset = 0x08;
|
||||
num_samples = 0;
|
||||
loop_start = read_32bitLE(0x00,streamFile);
|
||||
loop_end = read_32bitLE(0x04,streamFile); /* 0 if no loop */
|
||||
|
||||
if (loop_start > loop_end)
|
||||
goto fail; /* just in case */
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Bandai Namco Opus (found in NUS3Banks) [Taiko no Tatsujin: Nintendo Switch Version!] */
|
||||
VGMSTREAM * init_vgmstream_opus_nus3(STREAMFILE *streamFile) {
|
||||
off_t offset = 0;
|
||||
int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag;
|
||||
|
||||
/* checks */
|
||||
/* .opus: header ID (they only exist inside .nus3bank) */
|
||||
if (!check_extensions(streamFile, "opus,lopus"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, streamFile) != 0x4F505553) /* "OPUS" */
|
||||
goto fail;
|
||||
|
||||
/* Here's an interesting quirk, OPUS header contains big endian values
|
||||
while the Nintendo Opus header and data that follows remain little endian as usual */
|
||||
offset = read_32bitBE(0x20, streamFile);
|
||||
num_samples = read_32bitBE(0x08, streamFile);
|
||||
|
||||
/* Check if there's a loop end value to determine loop_flag*/
|
||||
loop_flag = read_32bitBE(0x18, streamFile);
|
||||
if (loop_flag) {
|
||||
loop_start = read_32bitBE(0x14, streamFile);
|
||||
loop_end = read_32bitBE(0x18, streamFile);
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Nippon Ichi SPS wrapper (non-segmented) [Ys VIII: Lacrimosa of Dana (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_sps_n1(STREAMFILE *streamFile) {
|
||||
off_t offset;
|
||||
int num_samples, loop_start = 0, loop_end = 0, loop_flag;
|
||||
|
||||
/* checks */
|
||||
/* .sps: Labyrinth of Refrain: Coven of Dusk (Switch)
|
||||
* .nlsd: Disgaea Refine (Switch), Ys VIII (Switch)
|
||||
* .at9: void tRrLM(); //Void Terrarium (Switch) */
|
||||
if (!check_extensions(streamFile, "sps,nlsd,at9"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, streamFile) != 0x09000000) /* file type (see other N1 SPS) */
|
||||
goto fail;
|
||||
|
||||
num_samples = read_32bitLE(0x0C, streamFile);
|
||||
|
||||
if ( read_32bitLE(0x1c, streamFile) == 0x01000080) {
|
||||
offset = 0x1C;
|
||||
|
||||
/* older games loop section (remnant of segmented opus_sps_n1): */
|
||||
loop_start = read_32bitLE(0x10, streamFile); /* intro samples */
|
||||
loop_end = loop_start + read_32bitLE(0x14, streamFile); /* loop samples */
|
||||
/* 0x18: end samples (all must add up to num_samples) */
|
||||
loop_flag = read_32bitLE(0x18, streamFile); /* with loop disabled only loop_end has a value */
|
||||
}
|
||||
else {
|
||||
offset = 0x18;
|
||||
|
||||
/* newer games loop section: */
|
||||
loop_start = read_32bitLE(0x10, streamFile);
|
||||
loop_end = read_32bitLE(0x14, streamFile);
|
||||
loop_flag = loop_start != loop_end; /* with loop disabled start and end are the same as num samples */
|
||||
}
|
||||
|
||||
if (!loop_flag) {
|
||||
loop_start = 0;
|
||||
loop_end = 0;
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* AQUASTYLE wrapper [Touhou Genso Wanderer -Reloaded- (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_opusx(STREAMFILE *streamFile) {
|
||||
off_t offset;
|
||||
int num_samples, loop_start = 0, loop_end = 0;
|
||||
float modifier;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "opusx"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, streamFile) != 0x4F505553) /* "OPUS" */
|
||||
goto fail;
|
||||
|
||||
offset = 0x10;
|
||||
/* values are for the original 44100 files, but Opus resamples to 48000 */
|
||||
modifier = 48000.0f / 44100.0f;
|
||||
num_samples = 0;//read_32bitLE(0x04, streamFile) * modifier; /* better use calc'd num_samples */
|
||||
loop_start = read_32bitLE(0x08, streamFile) * modifier;
|
||||
loop_end = read_32bitLE(0x0c, streamFile) * modifier;
|
||||
|
||||
/* resampling calcs are slighly off and may to over num_samples, but by removing delay seems ok */
|
||||
if (loop_start >= 120) {
|
||||
loop_start -= 128;
|
||||
loop_end -= 128;
|
||||
}
|
||||
else {
|
||||
loop_end = 0;
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Prototype variation [Clannad (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_prototype(STREAMFILE *streamFile) {
|
||||
off_t offset = 0;
|
||||
int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "opus,lopus"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00, streamFile) != 0x4F505553 || /* "OPUS" */
|
||||
read_32bitBE(0x18, streamFile) != 0x01000080)
|
||||
goto fail;
|
||||
|
||||
offset = 0x18;
|
||||
num_samples = read_32bitLE(0x08, streamFile);
|
||||
|
||||
/* Check if there's a loop end value to determine loop_flag*/
|
||||
loop_flag = read_32bitLE(0x10, streamFile);
|
||||
if (loop_flag) {
|
||||
loop_start = read_32bitLE(0x0C, streamFile);
|
||||
loop_end = read_32bitLE(0x10, streamFile);
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Edelweiss variation [Astebreed (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_opusnx(STREAMFILE *streamFile) {
|
||||
off_t offset = 0;
|
||||
int num_samples = 0, loop_start = 0, loop_end = 0;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "opus,lopus"))
|
||||
goto fail;
|
||||
if (read_64bitBE(0x00, streamFile) != 0x4F5055534E580000) /* "OPUSNX\0\0" */
|
||||
goto fail;
|
||||
|
||||
offset = 0x10;
|
||||
num_samples = 0; //read_32bitLE(0x08, streamFile); /* samples with encoder delay */
|
||||
if (read_32bitLE(0x0c, streamFile) != 0)
|
||||
goto fail;
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Square Enix variation [Dragon Quest I-III (Switch)] */
|
||||
VGMSTREAM * init_vgmstream_opus_sqex(STREAMFILE *streamFile) {
|
||||
off_t offset = 0;
|
||||
int num_samples = 0, loop_start = 0, loop_end = 0, loop_flag;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "opus,lopus"))
|
||||
goto fail;
|
||||
if (read_64bitBE(0x00, streamFile) != 0x0100000002000000)
|
||||
goto fail;
|
||||
|
||||
offset = read_32bitLE(0x0C, streamFile);
|
||||
num_samples = read_32bitLE(0x1C, streamFile);
|
||||
|
||||
/* Check if there's a loop end value to determine loop_flag*/
|
||||
loop_flag = read_32bitLE(0x18, streamFile);
|
||||
if (loop_flag) {
|
||||
loop_start = read_32bitLE(0x14, streamFile);
|
||||
loop_end = read_32bitLE(0x18, streamFile);
|
||||
}
|
||||
|
||||
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples, loop_start, loop_end);
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
@ -1,130 +1,127 @@
|
||||
#ifndef _RIFF_OGG_STREAMFILE_H_
|
||||
#define _RIFF_OGG_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
typedef struct {
|
||||
off_t patch_offset;
|
||||
} riff_ogg_io_data;
|
||||
|
||||
static size_t riff_ogg_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, riff_ogg_io_data* data) {
|
||||
size_t bytes_read = streamfile->read(streamfile, dest, offset, length);
|
||||
|
||||
/* has garbage init Oggs pages, patch bad flag */
|
||||
if (data->patch_offset && data->patch_offset >= offset && data->patch_offset < offset + bytes_read) {
|
||||
VGM_ASSERT(dest[data->patch_offset - offset] != 0x02, "RIFF Ogg: bad patch offset at %lx\n", data->patch_offset);
|
||||
dest[data->patch_offset - offset] = 0x00;
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
static size_t ogg_get_page(uint8_t *buf, size_t bufsize, off_t offset, STREAMFILE *sf) {
|
||||
size_t segments, bytes, page_size;
|
||||
int i;
|
||||
|
||||
if (0x1b > bufsize) goto fail;
|
||||
bytes = read_streamfile(buf, offset, 0x1b, sf);
|
||||
if (bytes != 0x1b) goto fail;
|
||||
|
||||
segments = get_u8(buf + 0x1a);
|
||||
if (0x1b + segments > bufsize) goto fail;
|
||||
|
||||
bytes = read_streamfile(buf + 0x1b, offset + 0x1b, segments, sf);
|
||||
if (bytes != segments) goto fail;
|
||||
|
||||
page_size = 0x1b + segments;
|
||||
for (i = 0; i < segments; i++) {
|
||||
page_size += get_u8(buf + 0x1b + i);
|
||||
}
|
||||
|
||||
return page_size;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* patches Oggs with weirdness */
|
||||
static STREAMFILE* setup_riff_ogg_streamfile(STREAMFILE *sf, off_t start, size_t size) {
|
||||
off_t patch_offset = 0;
|
||||
size_t real_size = size;
|
||||
uint8_t buf[0x1000];
|
||||
|
||||
|
||||
/* initial page flag is repeated and causes glitches in decoders, find bad offset */
|
||||
//todo callback could patch on-the-fly by analyzing all "OggS", but is problematic due to arbitrary offsets
|
||||
{
|
||||
off_t offset = start;
|
||||
size_t page_size;
|
||||
off_t offset_limit = start + size; /* usually in the first 0x3000 but can be +0x100000 */
|
||||
//todo this doesn't seem to help much
|
||||
STREAMFILE *temp_sf = reopen_streamfile(sf, 0x100); /* use small-ish sf to avoid reading the whole thing */
|
||||
|
||||
/* first page is ok */
|
||||
page_size = ogg_get_page(buf, sizeof(buf), offset, temp_sf);
|
||||
offset += page_size;
|
||||
|
||||
while (offset < offset_limit) {
|
||||
page_size = ogg_get_page(buf, sizeof(buf), offset, temp_sf);
|
||||
if (page_size == 0) break;
|
||||
|
||||
if (get_u32be(buf + 0x00) != 0x4f676753) /* "OggS" */
|
||||
break;
|
||||
|
||||
if (get_u16be(buf + 0x04) == 0x0002) { /* start page flag */
|
||||
//;VGM_ASSERT(patch_offset > 0, "RIFF Ogg: found multiple repeated start pages\n");
|
||||
patch_offset = (offset - start) + 0x04 + 0x01; /* clamp'ed */
|
||||
break;
|
||||
}
|
||||
|
||||
offset += page_size;
|
||||
}
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
|
||||
if (patch_offset == 0)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* has a bunch of padding(?) pages at the end with no data nor flag that confuse decoders, find actual end */
|
||||
{
|
||||
size_t chunk_size = sizeof(buf); /* not worth testing more */
|
||||
size_t max_size = size;
|
||||
size_t pos;
|
||||
off_t read_offset = start + size - chunk_size;
|
||||
|
||||
pos = read_streamfile(buf, read_offset, chunk_size, sf);
|
||||
if (read_offset < 0 || pos <= 0x1a) return NULL;
|
||||
|
||||
pos -= 0x1a; /* at least one OggS page */
|
||||
while (pos > 0) {
|
||||
if (get_u32be(buf + pos + 0x00) == 0x4f676753) { /* "OggS" */
|
||||
|
||||
if (get_u16be(buf + pos + 0x04) == 0x0004) { /* last page flag is ok */
|
||||
real_size = max_size;
|
||||
break;
|
||||
}
|
||||
else { /* last page flag is wrong */
|
||||
max_size = size - (chunk_size - pos); /* update size up to this page */
|
||||
}
|
||||
}
|
||||
pos--;
|
||||
}
|
||||
}
|
||||
|
||||
/* actual custom streamfile init */
|
||||
{
|
||||
STREAMFILE *new_sf = NULL;
|
||||
riff_ogg_io_data io_data = {0};
|
||||
|
||||
io_data.patch_offset = patch_offset;
|
||||
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_clamp_streamfile_f(new_sf, start, real_size);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(riff_ogg_io_data), riff_ogg_io_read, NULL);
|
||||
return new_sf;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* VGM_USE_VORBIS */
|
||||
|
||||
#endif /* _RIFF_OGG_STREAMFILE_H_ */
|
||||
#ifndef _RIFF_OGG_STREAMFILE_H_
|
||||
#define _RIFF_OGG_STREAMFILE_H_
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
typedef struct {
|
||||
off_t patch_offset;
|
||||
} riff_ogg_io_data;
|
||||
|
||||
static size_t riff_ogg_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, riff_ogg_io_data* data) {
|
||||
size_t bytes = read_streamfile(dest, offset, length, sf);
|
||||
|
||||
/* has garbage init Oggs pages, patch bad flag */
|
||||
if (data->patch_offset && data->patch_offset >= offset && data->patch_offset < offset + bytes) {
|
||||
VGM_ASSERT(dest[data->patch_offset - offset] != 0x02, "RIFF Ogg: bad patch offset at %lx\n", data->patch_offset);
|
||||
dest[data->patch_offset - offset] = 0x00;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static size_t ogg_get_page(uint8_t *buf, size_t bufsize, off_t offset, STREAMFILE *sf) {
|
||||
size_t segments, bytes, page_size;
|
||||
int i;
|
||||
|
||||
if (0x1b > bufsize) goto fail;
|
||||
bytes = read_streamfile(buf, offset, 0x1b, sf);
|
||||
if (bytes != 0x1b) goto fail;
|
||||
|
||||
segments = get_u8(buf + 0x1a);
|
||||
if (0x1b + segments > bufsize) goto fail;
|
||||
|
||||
bytes = read_streamfile(buf + 0x1b, offset + 0x1b, segments, sf);
|
||||
if (bytes != segments) goto fail;
|
||||
|
||||
page_size = 0x1b + segments;
|
||||
for (i = 0; i < segments; i++) {
|
||||
page_size += get_u8(buf + 0x1b + i);
|
||||
}
|
||||
|
||||
return page_size;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* patches Ogg with weirdness */
|
||||
static STREAMFILE* setup_riff_ogg_streamfile(STREAMFILE *sf, off_t start, size_t size) {
|
||||
off_t patch_offset = 0;
|
||||
size_t real_size = size;
|
||||
uint8_t buf[0x1000];
|
||||
|
||||
|
||||
/* initial page flag is repeated and causes glitches in decoders, find bad offset */
|
||||
//todo callback could patch on-the-fly by analyzing all "OggS", but is problematic due to arbitrary offsets
|
||||
{
|
||||
off_t offset = start;
|
||||
size_t page_size;
|
||||
off_t offset_limit = start + size; /* usually in the first 0x3000 but can be +0x100000 */
|
||||
//todo this doesn't seem to help much
|
||||
STREAMFILE *temp_sf = reopen_streamfile(sf, 0x100); /* use small-ish sf to avoid reading the whole thing */
|
||||
|
||||
/* first page is ok */
|
||||
page_size = ogg_get_page(buf, sizeof(buf), offset, temp_sf);
|
||||
offset += page_size;
|
||||
|
||||
while (offset < offset_limit) {
|
||||
page_size = ogg_get_page(buf, sizeof(buf), offset, temp_sf);
|
||||
if (page_size == 0) break;
|
||||
|
||||
if (get_u32be(buf + 0x00) != 0x4f676753) /* "OggS" */
|
||||
break;
|
||||
|
||||
if (get_u16be(buf + 0x04) == 0x0002) { /* start page flag */
|
||||
//;VGM_ASSERT(patch_offset > 0, "RIFF Ogg: found multiple repeated start pages\n");
|
||||
patch_offset = (offset - start) + 0x04 + 0x01; /* clamp'ed */
|
||||
break;
|
||||
}
|
||||
|
||||
offset += page_size;
|
||||
}
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
|
||||
if (patch_offset == 0)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* has a bunch of padding(?) pages at the end with no data nor flag that confuse decoders, find actual end */
|
||||
{
|
||||
size_t chunk_size = sizeof(buf); /* not worth testing more */
|
||||
size_t max_size = size;
|
||||
size_t pos;
|
||||
off_t read_offset = start + size - chunk_size;
|
||||
|
||||
pos = read_streamfile(buf, read_offset, chunk_size, sf);
|
||||
if (read_offset < 0 || pos <= 0x1a) return NULL;
|
||||
|
||||
pos -= 0x1a; /* at least one OggS page */
|
||||
while (pos > 0) {
|
||||
if (get_u32be(buf + pos + 0x00) == 0x4f676753) { /* "OggS" */
|
||||
|
||||
if (get_u16be(buf + pos + 0x04) == 0x0004) { /* last page flag is ok */
|
||||
real_size = max_size;
|
||||
break;
|
||||
}
|
||||
else { /* last page flag is wrong */
|
||||
max_size = size - (chunk_size - pos); /* update size up to this page */
|
||||
}
|
||||
}
|
||||
pos--;
|
||||
}
|
||||
}
|
||||
|
||||
/* actual custom streamfile init */
|
||||
{
|
||||
STREAMFILE *new_sf = NULL;
|
||||
riff_ogg_io_data io_data = {0};
|
||||
|
||||
io_data.patch_offset = patch_offset;
|
||||
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_clamp_streamfile_f(new_sf, start, real_size);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(riff_ogg_io_data), riff_ogg_io_read, NULL);
|
||||
return new_sf;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _RIFF_OGG_STREAMFILE_H_ */
|
||||
|
@ -1,133 +1,24 @@
|
||||
#ifndef _SFH_STREAMFILE_H_
|
||||
#define _SFH_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
off_t stream_offset;
|
||||
size_t stream_size;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake offset */
|
||||
off_t physical_offset; /* actual offset */
|
||||
size_t block_size; /* current size */
|
||||
size_t skip_size; /* size from block start to reach data */
|
||||
size_t data_size; /* usable size in a block */
|
||||
|
||||
size_t logical_size;
|
||||
} sfh_io_data;
|
||||
|
||||
|
||||
static size_t sfh_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, sfh_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
|
||||
/* re-start when previous offset (can't map logical<>physical offsets) */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
}
|
||||
|
||||
/* read blocks */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
data->skip_size = 0x10; /* skip 0x10 garbage on every block */
|
||||
data->data_size = data->block_size - 0x10;
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t sfh_io_size(STREAMFILE *streamfile, sfh_io_data* data) {
|
||||
uint8_t buf[1];
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
sfh_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* Handles deinterleaving of SFH blocked streams */
|
||||
static STREAMFILE* setup_sfh_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t block_size, size_t clean_size, const char* extension) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
sfh_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(sfh_io_data);
|
||||
|
||||
io_data.stream_offset = stream_offset;
|
||||
io_data.stream_size = get_streamfile_size(streamFile) - stream_offset;
|
||||
io_data.block_size = block_size;
|
||||
io_data.logical_offset = -1; /* force phys offset reset */
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, sfh_io_read,sfh_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_buffer_streamfile(new_streamFile,0);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_clamp_streamfile(new_streamFile,0x00, clean_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
if (extension) {
|
||||
new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,extension);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
}
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _SFH_STREAMFILE_H_ */
|
||||
#ifndef _SFH_STREAMFILE_H_
|
||||
#define _SFH_STREAMFILE_H_
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
/* Deblocks SFH streams, skipping 0x10 garbage added to every chunk */
|
||||
static STREAMFILE* setup_sfh_streamfile(STREAMFILE *sf, off_t stream_offset, size_t chunk_size, size_t clean_size, const char* extension) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
deblock_config_t cfg = {0};
|
||||
|
||||
cfg.stream_start = stream_offset;
|
||||
cfg.chunk_size = chunk_size;
|
||||
cfg.skip_size = 0x10;
|
||||
|
||||
/* setup sf */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
|
||||
//new_sf = open_buffer_streamfile_f(new_sf, 0);
|
||||
new_sf = open_clamp_streamfile_f(new_sf, 0x00, clean_size);
|
||||
if (extension)
|
||||
new_sf = open_fakename_streamfile_f(new_sf, NULL, extension);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _SFH_STREAMFILE_H_ */
|
||||
|
170
src/meta/vsv.c
170
src/meta/vsv.c
@ -1,85 +1,85 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "vsv_streamfile.h"
|
||||
|
||||
|
||||
/* .VSV - from Square Enix games [Dawn of Mana: Seiken Densetsu 4 (PS2), Kingdom Hearts Re:Chain of Memories (PS2)] */
|
||||
VGMSTREAM * init_vgmstream_vsv(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE *temp_streamFile = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, flags, sample_rate, is_rs;
|
||||
size_t loop_start, adjust, data_size, interleave;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .vsv: extension from internal filenames [KH Re:CoM (PS2), DoM (PS2), KH HD I.5 + II.5 ReMIX (PS4)]
|
||||
* .psh: fake */
|
||||
if (!check_extensions(streamFile, "vsv,psh"))
|
||||
goto fail;
|
||||
|
||||
/* 0x00(1x4): flags/config? */
|
||||
if ((uint8_t)read_8bit(0x03,streamFile) > 0x64) /* possibly volume */
|
||||
goto fail;
|
||||
if ((uint8_t)read_8bit(0x0a,streamFile) != 0) /* not seen */
|
||||
goto fail;
|
||||
|
||||
/* Romancing SaGa (PS2) uses an earlier? version, this seems to work */
|
||||
is_rs = ((uint16_t)read_16bitLE(0x00,streamFile) == 0);
|
||||
|
||||
start_offset = 0x00; /* correct, but needs some tricks to fix sound (see below) */
|
||||
interleave = 0x800;
|
||||
|
||||
adjust = (uint16_t)read_16bitLE(0x04,streamFile);
|
||||
loop_start = ((uint16_t)read_16bitLE(0x06,streamFile) & 0x7FFF) * interleave;
|
||||
loop_flag = (uint16_t)read_16bitLE(0x06,streamFile) & 0x8000; /* loop_start != 0 works too, no files loop from beginning to end */
|
||||
sample_rate = (uint16_t)read_16bitLE(0x08,streamFile);
|
||||
flags = (uint8_t)read_8bit (0x0b,streamFile); /* values: 0x01=stereo, 0x10=mono */
|
||||
data_size = (uint16_t)read_16bitLE(0x0c,streamFile) * interleave;
|
||||
/* 0x0e: ? (may be a low-ish value) */
|
||||
|
||||
channel_count = (flags & 1) ? 2 : 1;
|
||||
|
||||
/* must discard to avoid wrong loops and unwanted data (easier to see in voices) */
|
||||
if (!is_rs) { /* RS doesn't do this */
|
||||
/* adjust & 0xF800 is unknown (values=0x0000|0x0800|0xF800, can be mono/stereo, loop/no, adjust/no) */
|
||||
size_t discard = adjust & 0x07FF;
|
||||
/* at (file_end - 0x800 + discard) is a 0x03 PS flag to check this (adjust 0 does discard full block) */
|
||||
data_size -= (0x800 - discard) * channel_count;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_VSV;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count);
|
||||
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channel_count);
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
|
||||
/* these loops are odd, but comparing the audio wave with the OSTs values seem correct */
|
||||
if (is_rs) {
|
||||
vgmstream->loop_start_sample -= ps_bytes_to_samples(channel_count*interleave,channel_count); /* maybe *before* loop block? */
|
||||
vgmstream->loop_start_sample -= ps_bytes_to_samples(0x200*channel_count,channel_count); /* maybe default adjust? */
|
||||
}
|
||||
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
|
||||
temp_streamFile = setup_vsv_streamfile(streamFile, start_offset, data_size);
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, temp_streamFile, start_offset))
|
||||
goto fail;
|
||||
|
||||
close_streamfile(temp_streamFile);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "vsv_streamfile.h"
|
||||
|
||||
|
||||
/* .VSV - from Square Enix games [Dawn of Mana: Seiken Densetsu 4 (PS2), Kingdom Hearts Re:Chain of Memories (PS2)] */
|
||||
VGMSTREAM * init_vgmstream_vsv(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE *temp_streamFile = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, flags, sample_rate, is_rs;
|
||||
size_t loop_start, adjust, data_size, interleave;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .vsv: extension from internal filenames [KH Re:CoM (PS2), DoM (PS2), KH HD I.5 + II.5 ReMIX (PS4)]
|
||||
* .psh: fake */
|
||||
if (!check_extensions(streamFile, "vsv,psh"))
|
||||
goto fail;
|
||||
|
||||
/* 0x00(1x4): flags/config? */
|
||||
if ((uint8_t)read_8bit(0x03,streamFile) > 0x64) /* possibly volume */
|
||||
goto fail;
|
||||
if ((uint8_t)read_8bit(0x0a,streamFile) != 0) /* not seen */
|
||||
goto fail;
|
||||
|
||||
/* Romancing SaGa (PS2) uses an earlier? version, this seems to work */
|
||||
is_rs = ((uint16_t)read_16bitLE(0x00,streamFile) == 0);
|
||||
|
||||
start_offset = 0x00; /* correct, but needs some tricks to fix sound (see below) */
|
||||
interleave = 0x800;
|
||||
|
||||
adjust = (uint16_t)read_16bitLE(0x04,streamFile);
|
||||
loop_start = ((uint16_t)read_16bitLE(0x06,streamFile) & 0x7FFF) * interleave;
|
||||
loop_flag = (uint16_t)read_16bitLE(0x06,streamFile) & 0x8000; /* loop_start != 0 works too, no files loop from beginning to end */
|
||||
sample_rate = (uint16_t)read_16bitLE(0x08,streamFile);
|
||||
flags = (uint8_t)read_8bit (0x0b,streamFile); /* values: 0x01=stereo, 0x10=mono */
|
||||
data_size = (uint16_t)read_16bitLE(0x0c,streamFile) * interleave;
|
||||
/* 0x0e: ? (may be a low-ish value) */
|
||||
|
||||
channel_count = (flags & 1) ? 2 : 1;
|
||||
|
||||
/* must discard to avoid wrong loops and unwanted data (easier to see in voices) */
|
||||
if (!is_rs) { /* RS doesn't do this */
|
||||
/* adjust & 0xF800 is unknown (values=0x0000|0x0800|0xF800, can be mono/stereo, loop/no, adjust/no) */
|
||||
size_t discard = adjust & 0x07FF;
|
||||
/* at (file_end - 0x800 + discard) is a 0x03 PS flag to check this (adjust 0 does discard full block) */
|
||||
data_size -= (0x800 - discard) * channel_count;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_VSV;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count);
|
||||
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channel_count);
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
|
||||
/* these loops are odd, but comparing the audio wave with the OSTs values seem correct */
|
||||
if (is_rs) {
|
||||
vgmstream->loop_start_sample -= ps_bytes_to_samples(channel_count*interleave,channel_count); /* maybe *before* loop block? */
|
||||
vgmstream->loop_start_sample -= ps_bytes_to_samples(0x200*channel_count,channel_count); /* maybe default adjust? */
|
||||
}
|
||||
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
|
||||
temp_streamFile = setup_vsv_streamfile(streamFile);
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, temp_streamFile, start_offset))
|
||||
goto fail;
|
||||
|
||||
close_streamfile(temp_streamFile);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -1,53 +1,40 @@
|
||||
#ifndef _VSV_STREAMFILE_H_
|
||||
#define _VSV_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
typedef struct {
|
||||
off_t null_offset;
|
||||
} vsv_io_data;
|
||||
|
||||
static size_t vsv_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, vsv_io_data* data) {
|
||||
size_t bytes_read;
|
||||
int i;
|
||||
|
||||
bytes_read = streamfile->read(streamfile, dest, offset, length);
|
||||
|
||||
/* VSVs do start at 0x00, but first line is also the header; must null it to avoid clicks */
|
||||
if (offset < data->null_offset) {
|
||||
int max = data->null_offset - offset;
|
||||
if (max > bytes_read)
|
||||
max = bytes_read;
|
||||
|
||||
for (i = 0; i < max; i++) {
|
||||
dest[i] = 0;
|
||||
}
|
||||
}
|
||||
/* VSV also has last 0x800 block with a PS-ADPCM flag of 0x10 (incorrect), but it's ignored by the decoder */
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
static STREAMFILE* setup_vsv_streamfile(STREAMFILE *streamFile, off_t start_offset, size_t data_size) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
vsv_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(vsv_io_data);
|
||||
|
||||
io_data.null_offset = 0x10;
|
||||
|
||||
/* setup custom streamfile */
|
||||
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, vsv_io_read,NULL);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _VSV_STREAMFILE_H_ */
|
||||
#ifndef _VSV_STREAMFILE_H_
|
||||
#define _VSV_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
typedef struct {
|
||||
off_t null_offset;
|
||||
} vsv_io_data;
|
||||
|
||||
static size_t vsv_io_read(STREAMFILE *sf, uint8_t *dest, off_t offset, size_t length, vsv_io_data *data) {
|
||||
int i;
|
||||
size_t bytes = read_streamfile(dest, offset, length, sf);
|
||||
|
||||
/* VSVs do start at 0x00, but first line is also the header; must null it to avoid clicks */
|
||||
if (offset < data->null_offset) {
|
||||
int max = data->null_offset - offset;
|
||||
if (max > bytes)
|
||||
max = bytes;
|
||||
|
||||
for (i = 0; i < max; i++) {
|
||||
dest[i] = 0;
|
||||
}
|
||||
}
|
||||
/* VSV also has last 0x800 block with a PS-ADPCM flag of 0x10 (incorrect), but it's ignored by the decoder */
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/* cleans VSV data */
|
||||
static STREAMFILE* setup_vsv_streamfile(STREAMFILE *sf) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
vsv_io_data io_data = {0};
|
||||
|
||||
io_data.null_offset = 0x10;
|
||||
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data, sizeof(vsv_io_data), vsv_io_read, NULL);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _VSV_STREAMFILE_H_ */
|
||||
|
@ -1,160 +1,61 @@
|
||||
#ifndef _XAVS_STREAMFILE_H_
|
||||
#define _XAVS_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
off_t stream_offset;
|
||||
size_t stream_size;
|
||||
int stream_number;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake offset */
|
||||
off_t physical_offset; /* actual offset */
|
||||
size_t block_size; /* current size */
|
||||
size_t skip_size; /* size from block start to reach data */
|
||||
size_t data_size; /* usable size in a block */
|
||||
|
||||
size_t logical_size;
|
||||
} xavs_io_data;
|
||||
|
||||
|
||||
static size_t xavs_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, xavs_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
|
||||
/* re-start when previous offset (can't map logical<>physical offsets) */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
}
|
||||
|
||||
/* read blocks */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
uint32_t chunk_id = read_32bitLE(data->physical_offset+0x00, streamfile) & 0xFF;
|
||||
uint32_t chunk_size = read_32bitLE(data->physical_offset+0x00, streamfile) >> 8;
|
||||
|
||||
data->skip_size = 0x04;
|
||||
|
||||
switch(chunk_id) {
|
||||
/* audio */
|
||||
case 0x41:
|
||||
case 0x61:
|
||||
case 0x62:
|
||||
case 0x63:
|
||||
data->block_size = 0x04 + chunk_size;
|
||||
if (data->stream_number + 1 == (chunk_id & 0x0F)) {
|
||||
data->data_size = chunk_size;
|
||||
} else {
|
||||
data->data_size = 0; /* ignore other subsongs */
|
||||
}
|
||||
break;
|
||||
|
||||
/* video */
|
||||
case 0x56:
|
||||
data->block_size = 0x04 + chunk_size;
|
||||
data->data_size = 0;
|
||||
break;
|
||||
|
||||
/* empty */
|
||||
case 0x21: /* related to video */
|
||||
case 0x5F: /* "_EOS" */
|
||||
data->block_size = 0x04;
|
||||
data->data_size = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
VGM_LOG("XAVS: unknown type at %lx\n", data->physical_offset);
|
||||
data->block_size = 0x04;
|
||||
data->data_size = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t xavs_io_size(STREAMFILE *streamfile, xavs_io_data* data) {
|
||||
uint8_t buf[1];
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
xavs_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* Handles deinterleaving of XAVS blocked streams */
|
||||
static STREAMFILE* setup_xavs_streamfile(STREAMFILE *streamFile, off_t stream_offset, int stream_number) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
xavs_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(xavs_io_data);
|
||||
|
||||
io_data.stream_offset = stream_offset;
|
||||
io_data.stream_size = get_streamfile_size(streamFile) - stream_offset;
|
||||
io_data.stream_number = stream_number;
|
||||
io_data.logical_offset = -1; /* force phys offset reset */
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, xavs_io_read,xavs_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_buffer_streamfile(new_streamFile,0);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _XAVS_STREAMFILE_H_ */
|
||||
#ifndef _XAVS_STREAMFILE_H_
|
||||
#define _XAVS_STREAMFILE_H_
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
static void block_callback(STREAMFILE *sf, deblock_io_data *data) {
|
||||
uint32_t chunk_type = read_u32le(data->physical_offset, sf) & 0xFF;
|
||||
uint32_t chunk_size = read_u32le(data->physical_offset, sf) >> 8;
|
||||
|
||||
data->skip_size = 0x04;
|
||||
|
||||
switch(chunk_type) {
|
||||
/* audio */
|
||||
case 0x41:
|
||||
case 0x61:
|
||||
case 0x62:
|
||||
case 0x63:
|
||||
data->block_size = 0x04 + chunk_size;
|
||||
if (data->cfg.track_number + 1 == (chunk_type & 0x0F)) {
|
||||
data->data_size = chunk_size;
|
||||
} else {
|
||||
data->data_size = 0; /* ignore other subsongs */
|
||||
}
|
||||
break;
|
||||
|
||||
/* video */
|
||||
case 0x56:
|
||||
data->block_size = 0x04 + chunk_size;
|
||||
data->data_size = 0;
|
||||
break;
|
||||
|
||||
/* empty */
|
||||
case 0x21: /* related to video */
|
||||
case 0x5F: /* "_EOS" */
|
||||
data->block_size = 0x04;
|
||||
data->data_size = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
VGM_LOG("XAVS: unknown type at %lx\n", data->physical_offset);
|
||||
data->block_size = 0x04;
|
||||
data->data_size = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Deblocks XAVS video/audio data */
|
||||
static STREAMFILE* setup_xavs_streamfile(STREAMFILE *sf, off_t stream_offset, int stream_number) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
deblock_config_t cfg = {0};
|
||||
|
||||
cfg.stream_start = stream_offset;
|
||||
cfg.track_number = stream_number;
|
||||
cfg.block_callback = block_callback;
|
||||
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
|
||||
//new_sf = open_buffer_streamfile(new_sf,0);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _XAVS_STREAMFILE_H_ */
|
||||
|
@ -74,17 +74,30 @@ VGMSTREAM * init_vgmstream_xwc(STREAMFILE *streamFile) {
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case 0x584D4100: { /* "XMA\0" (X360) */
|
||||
uint8_t buf[0x100];
|
||||
int32_t bytes, seek_size, block_size, block_count, sample_rate;
|
||||
int32_t bytes, seek_size, block_size, block_count, sample_rate, chunk_size;
|
||||
|
||||
seek_size = read_32bitLE(extra_offset+0x00, streamFile);
|
||||
start_offset = extra_offset+0x04 + seek_size + read_32bitLE(extra_offset+0x04+seek_size, streamFile) + 0x08;
|
||||
seek_size = read_32bitLE(extra_offset + 0x00, streamFile);
|
||||
chunk_size = read_32bitLE(extra_offset + 0x04 + seek_size, streamFile);
|
||||
|
||||
start_offset = extra_offset+ 0x04 + seek_size + chunk_size + 0x08;
|
||||
start_offset += (start_offset % 0x800) ? 0x800 - (start_offset % 0x800) : 0; /* padded */
|
||||
data_size = data_size - start_offset;
|
||||
|
||||
sample_rate = read_32bitBE(extra_offset+0x04+seek_size+0x10, streamFile);
|
||||
block_size = read_32bitBE(extra_offset+0x04+seek_size+0x1c, streamFile);
|
||||
block_count = read_32bitBE(extra_offset+0x04+seek_size+0x28, streamFile);
|
||||
/* others: scrambled RIFF fmt BE values */
|
||||
if (chunk_size == 0x34) { /* new XMA2 */
|
||||
sample_rate = read_32bitLE(extra_offset+0x04+seek_size+0x08, streamFile);
|
||||
block_size = read_32bitLE(extra_offset+0x04+seek_size+0x20, streamFile);
|
||||
block_count = data_size / block_size;
|
||||
/* others: standard RIFF XMA2 fmt? */
|
||||
}
|
||||
else if (chunk_size == 0x2c) { /* old XMA2 */
|
||||
sample_rate = read_32bitBE(extra_offset+0x04+seek_size+0x10, streamFile);
|
||||
block_size = read_32bitBE(extra_offset+0x04+seek_size+0x1c, streamFile);
|
||||
block_count = read_32bitBE(extra_offset+0x04+seek_size+0x28, streamFile);
|
||||
/* others: scrambled RIFF fmt BE values */
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
bytes = ffmpeg_make_riff_xma2(buf,0x100, vgmstream->num_samples, data_size, vgmstream->channels, sample_rate, block_count, block_size);
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(streamFile, buf,bytes, start_offset,data_size);
|
||||
|
@ -3,13 +3,13 @@
|
||||
#include "zsnd_streamfile.h"
|
||||
|
||||
|
||||
/* ZSND - Vicarious Visions games [X-Men Legends II (multi), Marvel Ultimate Alliance (multi)] */
|
||||
/* ZSND - Z-Axis/Vicarious Visions games [X-Men Legends II (multi), Marvel Ultimate Alliance (multi)] */
|
||||
VGMSTREAM * init_vgmstream_zsnd(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE *temp_streamFile = NULL;
|
||||
off_t start_offset, name_offset;
|
||||
size_t stream_size, name_size;
|
||||
int loop_flag, channel_count, sample_rate, layers;
|
||||
int loop_flag, channel_count, sample_rate, layers, layers2 = 0;
|
||||
uint32_t codec;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
||||
@ -45,7 +45,7 @@ VGMSTREAM * init_vgmstream_zsnd(STREAMFILE *streamFile) {
|
||||
off_t header2_offset, header3_offset;
|
||||
int table2_entries, table3_entries;
|
||||
off_t table2_body, table3_body;
|
||||
int is_compact, i;
|
||||
int is_v1, i;
|
||||
|
||||
|
||||
/* multiple config tables:
|
||||
@ -68,11 +68,16 @@ VGMSTREAM * init_vgmstream_zsnd(STREAMFILE *streamFile) {
|
||||
* table1 may have more entries than table2/3, and sometimes isn't set
|
||||
*/
|
||||
|
||||
/* 'compact' mode has no table heads, rare [Aggresive Inline (Xbox)]
|
||||
/* V1 has no table heads, rare [Aggresive Inline (Xbox)]
|
||||
* no apparent flag but we can test if table heads offsets appear */
|
||||
is_compact = read_32bit(0x14,streamFile) > read_32bit(0x18,streamFile);
|
||||
is_v1 = read_32bit(0x14,streamFile) <= read_32bit(0x1c,streamFile) &&
|
||||
read_32bit(0x1c,streamFile) <= read_32bit(0x24,streamFile) &&
|
||||
read_32bit(0x24,streamFile) <= read_32bit(0x2c,streamFile) &&
|
||||
read_32bit(0x2c,streamFile) <= read_32bit(0x34,streamFile) &&
|
||||
read_32bit(0x34,streamFile) <= read_32bit(0x3c,streamFile) &&
|
||||
read_32bit(0x3c,streamFile) <= read_32bit(0x44,streamFile);
|
||||
|
||||
if (!is_compact) {
|
||||
if (!is_v1) {
|
||||
table2_entries = read_32bit(0x1c,streamFile);
|
||||
table2_body = read_32bit(0x24,streamFile);
|
||||
|
||||
@ -108,7 +113,7 @@ VGMSTREAM * init_vgmstream_zsnd(STREAMFILE *streamFile) {
|
||||
break;
|
||||
|
||||
case 0x58424F58: { /* "XBOX" */
|
||||
size_t entry2_size = is_compact ? 0x14 : 0x1c;
|
||||
size_t entry2_size = is_v1 || check_extensions(streamFile, "zsd") ? 0x14 : 0x1c;
|
||||
|
||||
/* BMX has unordered stream headers, and not every stream has a header */
|
||||
header2_offset = 0;
|
||||
@ -129,30 +134,51 @@ VGMSTREAM * init_vgmstream_zsnd(STREAMFILE *streamFile) {
|
||||
sample_rate = read_32bit(header2_offset + 0x04,streamFile);
|
||||
}
|
||||
else {
|
||||
/* defaults to this in cutscene files in BMX with no heads at all,
|
||||
* but also needs mono for speech files in Aggresive Inline */
|
||||
if (is_compact) {
|
||||
layers = 0x00;
|
||||
sample_rate = 16000;
|
||||
}
|
||||
else {
|
||||
layers = 0x02;
|
||||
sample_rate = 44100;
|
||||
}
|
||||
layers = 0;
|
||||
sample_rate = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
layers = read_16bit(header2_offset + 0x02,streamFile);
|
||||
sample_rate = read_32bit(header2_offset + 0x04,streamFile);
|
||||
if (entry2_size > 0x18) {
|
||||
layers2 = read_32bit(header2_offset + 0x18,streamFile);
|
||||
}
|
||||
}
|
||||
|
||||
header3_offset = table3_body + 0x54*(target_subsong-1);
|
||||
start_offset = read_32bit(header3_offset + 0x00,streamFile);
|
||||
stream_size = read_32bit(header3_offset + 0x04,streamFile);
|
||||
/* 0x08: flags? related to looping? (not channels) */
|
||||
//loop_end = read_32bit(header3_offset + 0x10,streamFile);
|
||||
name_offset = header3_offset + 0x14;
|
||||
name_size = 0x40;
|
||||
|
||||
/* early games sometimes don't seem to have info or headers, not sure how to detect better
|
||||
* ex. Aggresive Inline speech (1ch) vs music (2ch), or BMX cutscenes (2ch) */
|
||||
if (sample_rate == 0) {
|
||||
int is_music = 0;
|
||||
if (is_v1) {
|
||||
char filename[PATH_LIMIT];
|
||||
|
||||
/* stream length isn't enough */
|
||||
get_streamfile_filename(streamFile, filename, sizeof(filename));
|
||||
is_music = strcmp(filename, "music.zsd") == 0;
|
||||
}
|
||||
else {
|
||||
is_music = stream_size > 0x20000;
|
||||
}
|
||||
|
||||
if (is_music) {
|
||||
layers = 0x02;
|
||||
sample_rate = 44100;
|
||||
}
|
||||
else {
|
||||
layers = 0x00;
|
||||
sample_rate = is_v1 ? 16000 : 22050; /* some BMX need 16000 but can't detect? */
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@ -209,6 +235,10 @@ VGMSTREAM * init_vgmstream_zsnd(STREAMFILE *streamFile) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (layers2) {
|
||||
channel_count = channel_count * layers2;
|
||||
}
|
||||
|
||||
loop_flag = 0;
|
||||
}
|
||||
|
||||
|
@ -490,6 +490,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
|
||||
init_vgmstream_fda,
|
||||
init_vgmstream_tgc,
|
||||
init_vgmstream_kwb,
|
||||
init_vgmstream_lrmd,
|
||||
|
||||
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
|
||||
init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */
|
||||
|
@ -722,6 +722,7 @@ typedef enum {
|
||||
meta_FDA,
|
||||
meta_TGC,
|
||||
meta_KWB,
|
||||
meta_LRMD,
|
||||
} meta_t;
|
||||
|
||||
/* standard WAVEFORMATEXTENSIBLE speaker positions */
|
||||
|
Loading…
Reference in New Issue
Block a user