Apply new source files
This commit is contained in:
parent
42ea824972
commit
dd585311df
118 changed files with 26704 additions and 24051 deletions
|
@ -418,7 +418,8 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) {
|
|||
case coding_XBOX_IMA_int:
|
||||
case coding_FSB_IMA:
|
||||
case coding_WWISE_IMA:
|
||||
case coding_CD_IMA:
|
||||
case coding_CD_IMA: /* (0x24 - 0x04) * 2 */
|
||||
case coding_CRANKCASE_IMA: /* (0x23 - 0x3) * 2 */
|
||||
return 64;
|
||||
case coding_APPLE_IMA4:
|
||||
return 64;
|
||||
|
@ -654,6 +655,8 @@ int decode_get_frame_size(VGMSTREAM* vgmstream) {
|
|||
case coding_WWISE_IMA:
|
||||
case coding_CD_IMA:
|
||||
return 0x24;
|
||||
case coding_CRANKCASE_IMA:
|
||||
return 0x23;
|
||||
case coding_XBOX_IMA_mch:
|
||||
case coding_FSB_IMA:
|
||||
return 0x24 * vgmstream->channels;
|
||||
|
@ -1264,6 +1267,12 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
|
|||
vgmstream->channels, vgmstream->samples_into_block, samples_to_do);
|
||||
}
|
||||
break;
|
||||
case coding_CRANKCASE_IMA:
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
decode_crankcase_ima(&vgmstream->ch[ch], buffer+ch,
|
||||
vgmstream->channels, vgmstream->samples_into_block, samples_to_do);
|
||||
}
|
||||
break;
|
||||
|
||||
case coding_WS:
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
|
|
|
@ -1,454 +1,457 @@
|
|||
#include <ctype.h>
|
||||
#include "../vgmstream.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "mixing.h"
|
||||
#include "../util/channel_mappings.h"
|
||||
#include "../util/sf_utils.h"
|
||||
|
||||
/*******************************************************************************/
|
||||
/* TEXT */
|
||||
/*******************************************************************************/
|
||||
|
||||
static void describe_get_time(int32_t samples, int sample_rate, double* p_time_mm, double* p_time_ss) {
|
||||
double seconds = (double)samples / sample_rate;
|
||||
*p_time_mm = (int)(seconds / 60.0);
|
||||
*p_time_ss = seconds - *p_time_mm * 60.0f;
|
||||
if (*p_time_ss >= 59.999) /* avoid round up to 60.0 when printing to %06.3f */
|
||||
*p_time_ss = 59.999;
|
||||
}
|
||||
|
||||
/* Write a description of the stream into array pointed by desc, which must be length bytes long.
|
||||
* Will always be null-terminated if length > 0 */
|
||||
void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
|
||||
#define TEMPSIZE (256+32)
|
||||
char temp[TEMPSIZE];
|
||||
double time_mm, time_ss;
|
||||
|
||||
desc[0] = '\0';
|
||||
|
||||
if (!vgmstream) {
|
||||
snprintf(temp,TEMPSIZE, "NULL VGMSTREAM");
|
||||
concatn(length,desc,temp);
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(temp,TEMPSIZE, "sample rate: %d Hz\n", vgmstream->sample_rate);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
snprintf(temp,TEMPSIZE, "channels: %d\n", vgmstream->channels);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
{
|
||||
int output_channels = 0;
|
||||
mixing_info(vgmstream, NULL, &output_channels);
|
||||
|
||||
if (output_channels != vgmstream->channels) {
|
||||
snprintf(temp,TEMPSIZE, "input channels: %d\n", vgmstream->channels); /* repeated but mainly for plugins */
|
||||
concatn(length,desc,temp);
|
||||
snprintf(temp,TEMPSIZE, "output channels: %d\n", output_channels);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
}
|
||||
|
||||
if (vgmstream->channel_layout) {
|
||||
int cl = vgmstream->channel_layout;
|
||||
|
||||
/* not "channel layout: " to avoid mixups with "layout: " */
|
||||
snprintf(temp,TEMPSIZE, "channel mask: 0x%x /", vgmstream->channel_layout);
|
||||
concatn(length,desc,temp);
|
||||
if (cl & speaker_FL) concatn(length,desc," FL");
|
||||
if (cl & speaker_FR) concatn(length,desc," FR");
|
||||
if (cl & speaker_FC) concatn(length,desc," FC");
|
||||
if (cl & speaker_LFE) concatn(length,desc," LFE");
|
||||
if (cl & speaker_BL) concatn(length,desc," BL");
|
||||
if (cl & speaker_BR) concatn(length,desc," BR");
|
||||
if (cl & speaker_FLC) concatn(length,desc," FLC");
|
||||
if (cl & speaker_FRC) concatn(length,desc," FRC");
|
||||
if (cl & speaker_BC) concatn(length,desc," BC");
|
||||
if (cl & speaker_SL) concatn(length,desc," SL");
|
||||
if (cl & speaker_SR) concatn(length,desc," SR");
|
||||
if (cl & speaker_TC) concatn(length,desc," TC");
|
||||
if (cl & speaker_TFL) concatn(length,desc," TFL");
|
||||
if (cl & speaker_TFC) concatn(length,desc," TFC");
|
||||
if (cl & speaker_TFR) concatn(length,desc," TFR");
|
||||
if (cl & speaker_TBL) concatn(length,desc," TBL");
|
||||
if (cl & speaker_TBC) concatn(length,desc," TBC");
|
||||
if (cl & speaker_TBR) concatn(length,desc," TBR");
|
||||
concatn(length,desc,"\n");
|
||||
}
|
||||
|
||||
/* times mod sounds avoid round up to 60.0 */
|
||||
if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) {
|
||||
if (!vgmstream->loop_flag) {
|
||||
concatn(length,desc,"looping: disabled\n");
|
||||
}
|
||||
|
||||
describe_get_time(vgmstream->loop_start_sample, vgmstream->sample_rate, &time_mm, &time_ss);
|
||||
snprintf(temp,TEMPSIZE, "loop start: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_start_sample, time_mm, time_ss);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
describe_get_time(vgmstream->loop_end_sample, vgmstream->sample_rate, &time_mm, &time_ss);
|
||||
snprintf(temp,TEMPSIZE, "loop end: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_end_sample, time_mm, time_ss);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
|
||||
describe_get_time(vgmstream->num_samples, vgmstream->sample_rate, &time_mm, &time_ss);
|
||||
snprintf(temp,TEMPSIZE, "stream total samples: %d (%1.0f:%06.3f seconds)\n", vgmstream->num_samples, time_mm, time_ss);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
snprintf(temp,TEMPSIZE, "encoding: ");
|
||||
concatn(length,desc,temp);
|
||||
get_vgmstream_coding_description(vgmstream, temp, TEMPSIZE);
|
||||
concatn(length,desc,temp);
|
||||
concatn(length,desc,"\n");
|
||||
|
||||
snprintf(temp,TEMPSIZE, "layout: ");
|
||||
concatn(length,desc,temp);
|
||||
get_vgmstream_layout_description(vgmstream, temp, TEMPSIZE);
|
||||
concatn(length, desc, temp);
|
||||
concatn(length,desc,"\n");
|
||||
|
||||
if (vgmstream->layout_type == layout_interleave && vgmstream->channels > 1) {
|
||||
snprintf(temp,TEMPSIZE, "interleave: %#x bytes\n", (int32_t)vgmstream->interleave_block_size);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
if (vgmstream->interleave_first_block_size && vgmstream->interleave_first_block_size != vgmstream->interleave_block_size) {
|
||||
snprintf(temp,TEMPSIZE, "interleave first block: %#x bytes\n", (int32_t)vgmstream->interleave_first_block_size);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
|
||||
if (vgmstream->interleave_last_block_size && vgmstream->interleave_last_block_size != vgmstream->interleave_block_size) {
|
||||
snprintf(temp,TEMPSIZE, "interleave last block: %#x bytes\n", (int32_t)vgmstream->interleave_last_block_size);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
}
|
||||
|
||||
/* codecs with configurable frame size */
|
||||
if (vgmstream->frame_size > 0 || vgmstream->interleave_block_size > 0) {
|
||||
int32_t frame_size = vgmstream->frame_size > 0 ? vgmstream->frame_size : vgmstream->interleave_block_size;
|
||||
switch (vgmstream->coding_type) {
|
||||
case coding_MSADPCM:
|
||||
case coding_MSADPCM_int:
|
||||
case coding_MSADPCM_ck:
|
||||
case coding_MS_IMA:
|
||||
case coding_MC3:
|
||||
case coding_WWISE_IMA:
|
||||
case coding_REF_IMA:
|
||||
case coding_PSX_cfg:
|
||||
snprintf(temp,TEMPSIZE, "frame size: %#x bytes\n", frame_size);
|
||||
concatn(length,desc,temp);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(temp,TEMPSIZE, "metadata from: ");
|
||||
concatn(length,desc,temp);
|
||||
get_vgmstream_meta_description(vgmstream, temp, TEMPSIZE);
|
||||
concatn(length,desc,temp);
|
||||
concatn(length,desc,"\n");
|
||||
|
||||
snprintf(temp,TEMPSIZE, "bitrate: %d kbps\n", get_vgmstream_average_bitrate(vgmstream) / 1000);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
/* only interesting if more than one */
|
||||
if (vgmstream->num_streams > 1) {
|
||||
snprintf(temp,TEMPSIZE, "stream count: %d\n", vgmstream->num_streams);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
|
||||
if (vgmstream->num_streams > 1) {
|
||||
snprintf(temp,TEMPSIZE, "stream index: %d\n", vgmstream->stream_index == 0 ? 1 : vgmstream->stream_index);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
|
||||
if (vgmstream->stream_name[0] != '\0') {
|
||||
snprintf(temp,TEMPSIZE, "stream name: %s\n", vgmstream->stream_name);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
|
||||
if (vgmstream->config_enabled) {
|
||||
int32_t samples = vgmstream->pstate.play_duration;
|
||||
|
||||
describe_get_time(samples, vgmstream->sample_rate, &time_mm, &time_ss);
|
||||
snprintf(temp,TEMPSIZE, "play duration: %d samples (%1.0f:%06.3f seconds)\n", samples, time_mm, time_ss);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
}
|
||||
|
||||
void describe_vgmstream_info(VGMSTREAM* vgmstream, vgmstream_info* info) {
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(info, 0, sizeof(*info));
|
||||
|
||||
if (!vgmstream) {
|
||||
return;
|
||||
}
|
||||
|
||||
info->sample_rate = vgmstream->sample_rate;
|
||||
|
||||
info->channels = vgmstream->channels;
|
||||
|
||||
{
|
||||
int output_channels = 0;
|
||||
mixing_info(vgmstream, NULL, &output_channels);
|
||||
|
||||
if (output_channels != vgmstream->channels) {
|
||||
info->mixing_info.input_channels = vgmstream->channels;
|
||||
info->mixing_info.output_channels = output_channels;
|
||||
}
|
||||
}
|
||||
|
||||
info->channel_layout = vgmstream->channel_layout;
|
||||
|
||||
if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) {
|
||||
info->loop_info.start = vgmstream->loop_start_sample;
|
||||
info->loop_info.end = vgmstream->loop_end_sample;
|
||||
}
|
||||
|
||||
info->num_samples = vgmstream->num_samples;
|
||||
|
||||
get_vgmstream_coding_description(vgmstream, info->encoding, sizeof(info->encoding));
|
||||
|
||||
get_vgmstream_layout_description(vgmstream, info->layout, sizeof(info->layout));
|
||||
|
||||
if (vgmstream->layout_type == layout_interleave && vgmstream->channels > 1) {
|
||||
info->interleave_info.value = vgmstream->interleave_block_size;
|
||||
|
||||
if (vgmstream->interleave_first_block_size && vgmstream->interleave_first_block_size != vgmstream->interleave_block_size) {
|
||||
info->interleave_info.first_block = vgmstream->interleave_first_block_size;
|
||||
}
|
||||
|
||||
if (vgmstream->interleave_last_block_size && vgmstream->interleave_last_block_size != vgmstream->interleave_block_size) {
|
||||
info->interleave_info.last_block = vgmstream->interleave_last_block_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* codecs with configurable frame size */
|
||||
if (vgmstream->frame_size > 0 || vgmstream->interleave_block_size > 0) {
|
||||
int32_t frame_size = vgmstream->frame_size > 0 ? vgmstream->frame_size : vgmstream->interleave_block_size;
|
||||
switch (vgmstream->coding_type) {
|
||||
case coding_MSADPCM:
|
||||
case coding_MSADPCM_int:
|
||||
case coding_MSADPCM_ck:
|
||||
case coding_MS_IMA:
|
||||
case coding_MC3:
|
||||
case coding_WWISE_IMA:
|
||||
case coding_REF_IMA:
|
||||
case coding_PSX_cfg:
|
||||
info->frame_size = frame_size;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
get_vgmstream_meta_description(vgmstream, info->metadata, sizeof(info->metadata));
|
||||
|
||||
info->bitrate = get_vgmstream_average_bitrate(vgmstream);
|
||||
|
||||
/* only interesting if more than one */
|
||||
if (vgmstream->num_streams > 1) {
|
||||
info->stream_info.total = vgmstream->num_streams;
|
||||
}
|
||||
else {
|
||||
info->stream_info.total = 1;
|
||||
}
|
||||
|
||||
if (vgmstream->num_streams > 1) {
|
||||
info->stream_info.current = vgmstream->stream_index == 0 ? 1 : vgmstream->stream_index;
|
||||
}
|
||||
|
||||
if (vgmstream->stream_name[0] != '\0') {
|
||||
snprintf(info->stream_info.name, sizeof(info->stream_info.name), "%s", vgmstream->stream_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************/
|
||||
/* BITRATE */
|
||||
/*******************************************************************************/
|
||||
|
||||
#define BITRATE_FILES_MAX 128 /* arbitrary max, but +100 segments have been observed */
|
||||
typedef struct {
|
||||
uint32_t hash[BITRATE_FILES_MAX]; /* already used streamfiles */
|
||||
int subsong[BITRATE_FILES_MAX]; /* subsongs of those streamfiles (could be incorporated to the hash?) */
|
||||
int count;
|
||||
int count_max;
|
||||
} bitrate_info_t;
|
||||
|
||||
static uint32_t hash_sf(STREAMFILE* sf) {
|
||||
int i;
|
||||
char path[PATH_LIMIT];
|
||||
uint32_t hash = 2166136261;
|
||||
|
||||
get_streamfile_name(sf, path, sizeof(path));
|
||||
|
||||
/* our favorite garbo hash a.k.a FNV-1 32b */
|
||||
i = 0;
|
||||
while (path[i] != '\0') {
|
||||
char c = tolower(path[i]);
|
||||
hash = (hash * 16777619) ^ (uint8_t)c;
|
||||
i++;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/* average bitrate helper to get STREAMFILE for a channel, since some codecs may use their own */
|
||||
static STREAMFILE* get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM* vgmstream, int channel) {
|
||||
|
||||
if (vgmstream->coding_type == coding_NWA) {
|
||||
return nwa_get_streamfile(vgmstream->codec_data);
|
||||
}
|
||||
|
||||
if (vgmstream->coding_type == coding_ACM) {
|
||||
return acm_get_streamfile(vgmstream->codec_data);
|
||||
}
|
||||
|
||||
if (vgmstream->coding_type == coding_COMPRESSWAVE) {
|
||||
return compresswave_get_streamfile(vgmstream->codec_data);
|
||||
}
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
if (vgmstream->coding_type == coding_OGG_VORBIS) {
|
||||
return ogg_vorbis_get_streamfile(vgmstream->codec_data);
|
||||
}
|
||||
#endif
|
||||
if (vgmstream->coding_type == coding_CRI_HCA) {
|
||||
return hca_get_streamfile(vgmstream->codec_data);
|
||||
}
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
if (vgmstream->coding_type == coding_FFmpeg) {
|
||||
return ffmpeg_get_streamfile(vgmstream->codec_data);
|
||||
}
|
||||
#endif
|
||||
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
|
||||
if (vgmstream->coding_type == coding_MP4_AAC) {
|
||||
mp4_aac_codec_data *data = vgmstream->codec_data;
|
||||
return data ? data->if_file.streamfile : NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
return vgmstream->ch[channel].streamfile;
|
||||
}
|
||||
|
||||
static int get_vgmstream_file_bitrate_from_size(size_t size, int sample_rate, int32_t length_samples) {
|
||||
if (sample_rate == 0 || length_samples == 0) return 0;
|
||||
if (length_samples < 100) return 0; /* ignore stupid bitrates caused by some segments */
|
||||
return (int)((int64_t)size * 8 * sample_rate / length_samples);
|
||||
}
|
||||
static int get_vgmstream_file_bitrate_from_streamfile(STREAMFILE* sf, int sample_rate, int32_t length_samples) {
|
||||
if (sf == NULL) return 0;
|
||||
return get_vgmstream_file_bitrate_from_size(get_streamfile_size(sf), sample_rate, length_samples);
|
||||
}
|
||||
|
||||
static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, bitrate_info_t* br, int* p_uniques) {
|
||||
int i, ch;
|
||||
int bitrate = 0;
|
||||
|
||||
/* Recursively get bitrate and fill the list of streamfiles if needed (to filter),
|
||||
* since layouts can include further vgmstreams that may also share streamfiles.
|
||||
*
|
||||
* Because of how data, layers and segments can be combined it's possible to
|
||||
* fool this in various ways; metas should report stream_size in complex cases
|
||||
* to get accurate bitrates (particularly for subsongs). An edge case is when
|
||||
* segments use only a few samples from a full file (like Wwise transitions), bitrates
|
||||
* become a bit high since its hard to detect only part of the file is needed. */
|
||||
|
||||
if (vgmstream->stream_size != 0) {
|
||||
/* format may report full size for custom layouts that otherwise get odd values */
|
||||
bitrate += get_vgmstream_file_bitrate_from_size(vgmstream->stream_size, vgmstream->sample_rate, vgmstream->num_samples);
|
||||
if (p_uniques)
|
||||
(*p_uniques)++;
|
||||
}
|
||||
else if (vgmstream->layout_type == layout_segmented) {
|
||||
int uniques = 0;
|
||||
segmented_layout_data *data = (segmented_layout_data *) vgmstream->layout_data;
|
||||
for (i = 0; i < data->segment_count; i++) {
|
||||
bitrate += get_vgmstream_file_bitrate_main(data->segments[i], br, &uniques);
|
||||
}
|
||||
if (uniques)
|
||||
bitrate /= uniques; /* average */
|
||||
}
|
||||
else if (vgmstream->layout_type == layout_layered) {
|
||||
layered_layout_data *data = vgmstream->layout_data;
|
||||
for (i = 0; i < data->layer_count; i++) {
|
||||
bitrate += get_vgmstream_file_bitrate_main(data->layers[i], br, NULL);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Add channel bitrate if streamfile hasn't been used before, so bitrate doesn't count repeats
|
||||
* (like same STREAMFILE reopened per channel, also considering SFs may be wrapped). */
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
uint32_t hash_cur;
|
||||
int subsong_cur;
|
||||
STREAMFILE* sf_cur;
|
||||
int is_unique = 1; /* default to "no other SFs exist" */
|
||||
|
||||
/* compares paths (hashes for faster compares) + subsongs (same file + different subsong = "different" file) */
|
||||
sf_cur = get_vgmstream_average_bitrate_channel_streamfile(vgmstream, ch);
|
||||
if (!sf_cur) continue;
|
||||
|
||||
hash_cur = hash_sf(sf_cur);
|
||||
subsong_cur = vgmstream->stream_index;
|
||||
|
||||
for (i = 0; i < br->count; i++) {
|
||||
uint32_t hash_cmp = br->hash[i];
|
||||
int subsong_cmp = br->subsong[i];
|
||||
|
||||
if (hash_cur == hash_cmp && subsong_cur == subsong_cmp) {
|
||||
is_unique = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_unique) {
|
||||
size_t file_bitrate;
|
||||
|
||||
if (br->count >= br->count_max) goto fail;
|
||||
|
||||
if (vgmstream->stream_size) {
|
||||
/* stream_size applies to both channels but should add once and detect repeats (for current subsong) */
|
||||
file_bitrate = get_vgmstream_file_bitrate_from_size(vgmstream->stream_size, vgmstream->sample_rate, vgmstream->num_samples);
|
||||
}
|
||||
else {
|
||||
file_bitrate = get_vgmstream_file_bitrate_from_streamfile(sf_cur, vgmstream->sample_rate, vgmstream->num_samples);
|
||||
}
|
||||
|
||||
/* possible in cases like using silence codec */
|
||||
if (!file_bitrate)
|
||||
break;
|
||||
|
||||
br->hash[br->count] = hash_cur;
|
||||
br->subsong[br->count] = subsong_cur;
|
||||
|
||||
br->count++;
|
||||
if (p_uniques)
|
||||
(*p_uniques)++;
|
||||
|
||||
bitrate += file_bitrate;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bitrate;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return the average bitrate in bps of all unique data contained within this stream.
|
||||
* This is the bitrate of the *file*, as opposed to the bitrate of the *codec*, meaning
|
||||
* it counts extra data like block headers and padding. While this can be surprising
|
||||
* sometimes (as it's often higher than common codec bitrates) it isn't wrong per se. */
|
||||
int get_vgmstream_average_bitrate(VGMSTREAM* vgmstream) {
|
||||
bitrate_info_t br = {0};
|
||||
br.count_max = BITRATE_FILES_MAX;
|
||||
|
||||
return get_vgmstream_file_bitrate_main(vgmstream, &br, NULL);
|
||||
}
|
||||
#include <ctype.h>
|
||||
#include "../vgmstream.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "mixing.h"
|
||||
#include "../util/channel_mappings.h"
|
||||
#include "../util/sf_utils.h"
|
||||
|
||||
/*******************************************************************************/
|
||||
/* TEXT */
|
||||
/*******************************************************************************/
|
||||
|
||||
static void describe_get_time(int32_t samples, int sample_rate, double* p_time_mm, double* p_time_ss) {
|
||||
double seconds = (double)samples / sample_rate;
|
||||
*p_time_mm = (int)(seconds / 60.0);
|
||||
*p_time_ss = seconds - *p_time_mm * 60.0f;
|
||||
if (*p_time_ss >= 59.999) /* avoid round up to 60.0 when printing to %06.3f */
|
||||
*p_time_ss = 59.999;
|
||||
}
|
||||
|
||||
/* Write a description of the stream into array pointed by desc, which must be length bytes long.
|
||||
* Will always be null-terminated if length > 0 */
|
||||
void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
|
||||
#define TEMPSIZE (256+32)
|
||||
char temp[TEMPSIZE];
|
||||
double time_mm, time_ss;
|
||||
|
||||
desc[0] = '\0';
|
||||
|
||||
if (!vgmstream) {
|
||||
snprintf(temp,TEMPSIZE, "NULL VGMSTREAM");
|
||||
concatn(length,desc,temp);
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(temp,TEMPSIZE, "sample rate: %d Hz\n", vgmstream->sample_rate);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
snprintf(temp,TEMPSIZE, "channels: %d\n", vgmstream->channels);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
{
|
||||
int output_channels = 0;
|
||||
mixing_info(vgmstream, NULL, &output_channels);
|
||||
|
||||
if (output_channels != vgmstream->channels) {
|
||||
snprintf(temp,TEMPSIZE, "input channels: %d\n", vgmstream->channels); /* repeated but mainly for plugins */
|
||||
concatn(length,desc,temp);
|
||||
snprintf(temp,TEMPSIZE, "output channels: %d\n", output_channels);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
}
|
||||
|
||||
if (vgmstream->channel_layout) {
|
||||
int cl = vgmstream->channel_layout;
|
||||
|
||||
/* not "channel layout: " to avoid mixups with "layout: " */
|
||||
snprintf(temp,TEMPSIZE, "channel mask: 0x%x /", vgmstream->channel_layout);
|
||||
concatn(length,desc,temp);
|
||||
if (cl & speaker_FL) concatn(length,desc," FL");
|
||||
if (cl & speaker_FR) concatn(length,desc," FR");
|
||||
if (cl & speaker_FC) concatn(length,desc," FC");
|
||||
if (cl & speaker_LFE) concatn(length,desc," LFE");
|
||||
if (cl & speaker_BL) concatn(length,desc," BL");
|
||||
if (cl & speaker_BR) concatn(length,desc," BR");
|
||||
if (cl & speaker_FLC) concatn(length,desc," FLC");
|
||||
if (cl & speaker_FRC) concatn(length,desc," FRC");
|
||||
if (cl & speaker_BC) concatn(length,desc," BC");
|
||||
if (cl & speaker_SL) concatn(length,desc," SL");
|
||||
if (cl & speaker_SR) concatn(length,desc," SR");
|
||||
if (cl & speaker_TC) concatn(length,desc," TC");
|
||||
if (cl & speaker_TFL) concatn(length,desc," TFL");
|
||||
if (cl & speaker_TFC) concatn(length,desc," TFC");
|
||||
if (cl & speaker_TFR) concatn(length,desc," TFR");
|
||||
if (cl & speaker_TBL) concatn(length,desc," TBL");
|
||||
if (cl & speaker_TBC) concatn(length,desc," TBC");
|
||||
if (cl & speaker_TBR) concatn(length,desc," TBR");
|
||||
concatn(length,desc,"\n");
|
||||
}
|
||||
|
||||
/* times mod sounds avoid round up to 60.0 */
|
||||
if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) {
|
||||
if (!vgmstream->loop_flag) {
|
||||
concatn(length,desc,"looping: disabled\n");
|
||||
}
|
||||
|
||||
describe_get_time(vgmstream->loop_start_sample, vgmstream->sample_rate, &time_mm, &time_ss);
|
||||
snprintf(temp,TEMPSIZE, "loop start: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_start_sample, time_mm, time_ss);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
describe_get_time(vgmstream->loop_end_sample, vgmstream->sample_rate, &time_mm, &time_ss);
|
||||
snprintf(temp,TEMPSIZE, "loop end: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_end_sample, time_mm, time_ss);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
|
||||
describe_get_time(vgmstream->num_samples, vgmstream->sample_rate, &time_mm, &time_ss);
|
||||
snprintf(temp,TEMPSIZE, "stream total samples: %d (%1.0f:%06.3f seconds)\n", vgmstream->num_samples, time_mm, time_ss);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
snprintf(temp,TEMPSIZE, "encoding: ");
|
||||
concatn(length,desc,temp);
|
||||
get_vgmstream_coding_description(vgmstream, temp, TEMPSIZE);
|
||||
concatn(length,desc,temp);
|
||||
concatn(length,desc,"\n");
|
||||
|
||||
snprintf(temp,TEMPSIZE, "layout: ");
|
||||
concatn(length,desc,temp);
|
||||
get_vgmstream_layout_description(vgmstream, temp, TEMPSIZE);
|
||||
concatn(length, desc, temp);
|
||||
concatn(length,desc,"\n");
|
||||
|
||||
if (vgmstream->layout_type == layout_interleave && vgmstream->channels > 1) {
|
||||
snprintf(temp,TEMPSIZE, "interleave: %#x bytes\n", (int32_t)vgmstream->interleave_block_size);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
if (vgmstream->interleave_first_block_size && vgmstream->interleave_first_block_size != vgmstream->interleave_block_size) {
|
||||
snprintf(temp,TEMPSIZE, "interleave first block: %#x bytes\n", (int32_t)vgmstream->interleave_first_block_size);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
|
||||
if (vgmstream->interleave_last_block_size && vgmstream->interleave_last_block_size != vgmstream->interleave_block_size) {
|
||||
snprintf(temp,TEMPSIZE, "interleave last block: %#x bytes\n", (int32_t)vgmstream->interleave_last_block_size);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
}
|
||||
|
||||
/* codecs with configurable frame size */
|
||||
if (vgmstream->frame_size > 0 || vgmstream->interleave_block_size > 0) {
|
||||
int32_t frame_size = vgmstream->frame_size > 0 ? vgmstream->frame_size : vgmstream->interleave_block_size;
|
||||
switch (vgmstream->coding_type) {
|
||||
case coding_MSADPCM:
|
||||
case coding_MSADPCM_int:
|
||||
case coding_MSADPCM_ck:
|
||||
case coding_MS_IMA:
|
||||
case coding_MC3:
|
||||
case coding_WWISE_IMA:
|
||||
case coding_REF_IMA:
|
||||
case coding_PSX_cfg:
|
||||
snprintf(temp,TEMPSIZE, "frame size: %#x bytes\n", frame_size);
|
||||
concatn(length,desc,temp);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(temp,TEMPSIZE, "metadata from: ");
|
||||
concatn(length,desc,temp);
|
||||
get_vgmstream_meta_description(vgmstream, temp, TEMPSIZE);
|
||||
concatn(length,desc,temp);
|
||||
concatn(length,desc,"\n");
|
||||
|
||||
snprintf(temp,TEMPSIZE, "bitrate: %d kbps\n", get_vgmstream_average_bitrate(vgmstream) / 1000);
|
||||
concatn(length,desc,temp);
|
||||
|
||||
/* only interesting if more than one */
|
||||
if (vgmstream->num_streams > 1) {
|
||||
snprintf(temp,TEMPSIZE, "stream count: %d\n", vgmstream->num_streams);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
|
||||
if (vgmstream->num_streams > 1) {
|
||||
snprintf(temp,TEMPSIZE, "stream index: %d\n", vgmstream->stream_index == 0 ? 1 : vgmstream->stream_index);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
|
||||
if (vgmstream->stream_name[0] != '\0') {
|
||||
snprintf(temp,TEMPSIZE, "stream name: %s\n", vgmstream->stream_name);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
|
||||
if (vgmstream->config_enabled) {
|
||||
int32_t samples = vgmstream->pstate.play_duration;
|
||||
|
||||
describe_get_time(samples, vgmstream->sample_rate, &time_mm, &time_ss);
|
||||
snprintf(temp,TEMPSIZE, "play duration: %d samples (%1.0f:%06.3f seconds)\n", samples, time_mm, time_ss);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
}
|
||||
|
||||
void describe_vgmstream_info(VGMSTREAM* vgmstream, vgmstream_info* info) {
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(info, 0, sizeof(*info));
|
||||
|
||||
if (!vgmstream) {
|
||||
return;
|
||||
}
|
||||
|
||||
info->sample_rate = vgmstream->sample_rate;
|
||||
|
||||
info->channels = vgmstream->channels;
|
||||
|
||||
{
|
||||
int output_channels = 0;
|
||||
mixing_info(vgmstream, NULL, &output_channels);
|
||||
|
||||
if (output_channels != vgmstream->channels) {
|
||||
info->mixing_info.input_channels = vgmstream->channels;
|
||||
info->mixing_info.output_channels = output_channels;
|
||||
}
|
||||
}
|
||||
|
||||
info->channel_layout = vgmstream->channel_layout;
|
||||
|
||||
if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) {
|
||||
info->loop_info.start = vgmstream->loop_start_sample;
|
||||
info->loop_info.end = vgmstream->loop_end_sample;
|
||||
}
|
||||
|
||||
info->num_samples = vgmstream->num_samples;
|
||||
|
||||
get_vgmstream_coding_description(vgmstream, info->encoding, sizeof(info->encoding));
|
||||
|
||||
get_vgmstream_layout_description(vgmstream, info->layout, sizeof(info->layout));
|
||||
|
||||
if (vgmstream->layout_type == layout_interleave && vgmstream->channels > 1) {
|
||||
info->interleave_info.value = vgmstream->interleave_block_size;
|
||||
|
||||
if (vgmstream->interleave_first_block_size && vgmstream->interleave_first_block_size != vgmstream->interleave_block_size) {
|
||||
info->interleave_info.first_block = vgmstream->interleave_first_block_size;
|
||||
}
|
||||
|
||||
if (vgmstream->interleave_last_block_size && vgmstream->interleave_last_block_size != vgmstream->interleave_block_size) {
|
||||
info->interleave_info.last_block = vgmstream->interleave_last_block_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* codecs with configurable frame size */
|
||||
if (vgmstream->frame_size > 0 || vgmstream->interleave_block_size > 0) {
|
||||
int32_t frame_size = vgmstream->frame_size > 0 ? vgmstream->frame_size : vgmstream->interleave_block_size;
|
||||
switch (vgmstream->coding_type) {
|
||||
case coding_MSADPCM:
|
||||
case coding_MSADPCM_int:
|
||||
case coding_MSADPCM_ck:
|
||||
case coding_MS_IMA:
|
||||
case coding_MC3:
|
||||
case coding_WWISE_IMA:
|
||||
case coding_REF_IMA:
|
||||
case coding_PSX_cfg:
|
||||
info->frame_size = frame_size;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
get_vgmstream_meta_description(vgmstream, info->metadata, sizeof(info->metadata));
|
||||
|
||||
info->bitrate = get_vgmstream_average_bitrate(vgmstream);
|
||||
|
||||
/* only interesting if more than one */
|
||||
if (vgmstream->num_streams > 1) {
|
||||
info->stream_info.total = vgmstream->num_streams;
|
||||
}
|
||||
else {
|
||||
info->stream_info.total = 1;
|
||||
}
|
||||
|
||||
if (vgmstream->num_streams > 1) {
|
||||
info->stream_info.current = vgmstream->stream_index == 0 ? 1 : vgmstream->stream_index;
|
||||
}
|
||||
|
||||
if (vgmstream->stream_name[0] != '\0') {
|
||||
snprintf(info->stream_info.name, sizeof(info->stream_info.name), "%s", vgmstream->stream_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************/
|
||||
/* BITRATE */
|
||||
/*******************************************************************************/
|
||||
|
||||
#define BITRATE_FILES_MAX 128 /* arbitrary max, but +100 segments have been observed */
|
||||
typedef struct {
|
||||
uint32_t hash[BITRATE_FILES_MAX]; /* already used streamfiles */
|
||||
int subsong[BITRATE_FILES_MAX]; /* subsongs of those streamfiles (could be incorporated to the hash?) */
|
||||
int count;
|
||||
int count_max;
|
||||
} bitrate_info_t;
|
||||
|
||||
static uint32_t hash_sf(STREAMFILE* sf) {
|
||||
int i;
|
||||
char path[PATH_LIMIT];
|
||||
uint32_t hash = 2166136261;
|
||||
|
||||
get_streamfile_name(sf, path, sizeof(path));
|
||||
|
||||
/* our favorite garbo hash a.k.a FNV-1 32b */
|
||||
i = 0;
|
||||
while (path[i] != '\0') {
|
||||
char c = tolower(path[i]);
|
||||
hash = (hash * 16777619) ^ (uint8_t)c;
|
||||
i++;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/* average bitrate helper to get STREAMFILE for a channel, since some codecs may use their own */
|
||||
static STREAMFILE* get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM* vgmstream, int channel) {
|
||||
|
||||
if (vgmstream->coding_type == coding_NWA) {
|
||||
return nwa_get_streamfile(vgmstream->codec_data);
|
||||
}
|
||||
|
||||
if (vgmstream->coding_type == coding_ACM) {
|
||||
return acm_get_streamfile(vgmstream->codec_data);
|
||||
}
|
||||
|
||||
if (vgmstream->coding_type == coding_COMPRESSWAVE) {
|
||||
return compresswave_get_streamfile(vgmstream->codec_data);
|
||||
}
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
if (vgmstream->coding_type == coding_OGG_VORBIS) {
|
||||
return ogg_vorbis_get_streamfile(vgmstream->codec_data);
|
||||
}
|
||||
#endif
|
||||
if (vgmstream->coding_type == coding_CRI_HCA) {
|
||||
return hca_get_streamfile(vgmstream->codec_data);
|
||||
}
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
if (vgmstream->coding_type == coding_FFmpeg) {
|
||||
return ffmpeg_get_streamfile(vgmstream->codec_data);
|
||||
}
|
||||
#endif
|
||||
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
|
||||
if (vgmstream->coding_type == coding_MP4_AAC) {
|
||||
mp4_aac_codec_data *data = vgmstream->codec_data;
|
||||
return data ? data->if_file.streamfile : NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
return vgmstream->ch[channel].streamfile;
|
||||
}
|
||||
|
||||
static int get_vgmstream_file_bitrate_from_size(size_t size, int sample_rate, int32_t length_samples) {
|
||||
if (sample_rate == 0 || length_samples == 0) return 0;
|
||||
if (length_samples < 100) return 0; /* ignore stupid bitrates caused by some segments */
|
||||
return (int)((int64_t)size * 8 * sample_rate / length_samples);
|
||||
}
|
||||
static int get_vgmstream_file_bitrate_from_streamfile(STREAMFILE* sf, int sample_rate, int32_t length_samples) {
|
||||
if (sf == NULL) return 0;
|
||||
return get_vgmstream_file_bitrate_from_size(get_streamfile_size(sf), sample_rate, length_samples);
|
||||
}
|
||||
|
||||
static int get_vgmstream_file_bitrate_main(VGMSTREAM* vgmstream, bitrate_info_t* br, int* p_uniques) {
|
||||
int i, ch;
|
||||
int bitrate = 0;
|
||||
|
||||
/* Recursively get bitrate and fill the list of streamfiles if needed (to filter),
|
||||
* since layouts can include further vgmstreams that may also share streamfiles.
|
||||
*
|
||||
* Because of how data, layers and segments can be combined it's possible to
|
||||
* fool this in various ways; metas should report stream_size in complex cases
|
||||
* to get accurate bitrates (particularly for subsongs). An edge case is when
|
||||
* segments use only a few samples from a full file (like Wwise transitions), bitrates
|
||||
* become a bit high since its hard to detect only part of the file is needed. */
|
||||
|
||||
if (vgmstream->stream_size != 0) {
|
||||
/* format may report full size for custom layouts that otherwise get odd values */
|
||||
bitrate += get_vgmstream_file_bitrate_from_size(vgmstream->stream_size, vgmstream->sample_rate, vgmstream->num_samples);
|
||||
if (p_uniques)
|
||||
(*p_uniques)++;
|
||||
}
|
||||
else if (vgmstream->layout_type == layout_segmented) {
|
||||
int uniques = 0;
|
||||
segmented_layout_data *data = (segmented_layout_data *) vgmstream->layout_data;
|
||||
for (i = 0; i < data->segment_count; i++) {
|
||||
bitrate += get_vgmstream_file_bitrate_main(data->segments[i], br, &uniques);
|
||||
}
|
||||
if (uniques)
|
||||
bitrate /= uniques; /* average */
|
||||
}
|
||||
else if (vgmstream->layout_type == layout_layered) {
|
||||
layered_layout_data *data = vgmstream->layout_data;
|
||||
for (i = 0; i < data->layer_count; i++) {
|
||||
bitrate += get_vgmstream_file_bitrate_main(data->layers[i], br, NULL);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Add channel bitrate if streamfile hasn't been used before, so bitrate doesn't count repeats
|
||||
* (like same STREAMFILE reopened per channel, also considering SFs may be wrapped). */
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
uint32_t hash_cur;
|
||||
int subsong_cur;
|
||||
STREAMFILE* sf_cur;
|
||||
int is_unique = 1; /* default to "no other SFs exist" */
|
||||
|
||||
/* compares paths (hashes for faster compares) + subsongs (same file + different subsong = "different" file) */
|
||||
sf_cur = get_vgmstream_average_bitrate_channel_streamfile(vgmstream, ch);
|
||||
if (!sf_cur) continue;
|
||||
|
||||
hash_cur = hash_sf(sf_cur);
|
||||
subsong_cur = vgmstream->stream_index;
|
||||
|
||||
for (i = 0; i < br->count; i++) {
|
||||
uint32_t hash_cmp = br->hash[i];
|
||||
int subsong_cmp = br->subsong[i];
|
||||
|
||||
if (hash_cur == hash_cmp && subsong_cur == subsong_cmp) {
|
||||
is_unique = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_unique) {
|
||||
size_t file_bitrate;
|
||||
|
||||
if (br->count >= br->count_max) goto fail;
|
||||
|
||||
if (vgmstream->stream_size) {
|
||||
/* stream_size applies to both channels but should add once and detect repeats (for current subsong) */
|
||||
file_bitrate = get_vgmstream_file_bitrate_from_size(vgmstream->stream_size, vgmstream->sample_rate, vgmstream->num_samples);
|
||||
}
|
||||
else {
|
||||
file_bitrate = get_vgmstream_file_bitrate_from_streamfile(sf_cur, vgmstream->sample_rate, vgmstream->num_samples);
|
||||
}
|
||||
|
||||
/* possible in cases like using silence codec */
|
||||
if (!file_bitrate)
|
||||
break;
|
||||
|
||||
br->hash[br->count] = hash_cur;
|
||||
br->subsong[br->count] = subsong_cur;
|
||||
|
||||
br->count++;
|
||||
if (p_uniques)
|
||||
(*p_uniques)++;
|
||||
|
||||
bitrate += file_bitrate;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bitrate;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return the average bitrate in bps of all unique data contained within this stream.
|
||||
* This is the bitrate of the *file*, as opposed to the bitrate of the *codec*, meaning
|
||||
* it counts extra data like block headers and padding. While this can be surprising
|
||||
* sometimes (as it's often higher than common codec bitrates) it isn't wrong per se. */
|
||||
int get_vgmstream_average_bitrate(VGMSTREAM* vgmstream) {
|
||||
bitrate_info_t br = {0};
|
||||
br.count_max = BITRATE_FILES_MAX;
|
||||
|
||||
if (vgmstream->coding_type == coding_SILENCE)
|
||||
return 0;
|
||||
|
||||
return get_vgmstream_file_bitrate_main(vgmstream, &br, NULL);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ atrac9_codec_data* init_atrac9(atrac9_config* cfg) {
|
|||
data->handle = Atrac9GetHandle();
|
||||
if (!data->handle) goto fail;
|
||||
|
||||
put_32bitBE(config_data, cfg->config_data);
|
||||
put_u32be(config_data, cfg->config_data);
|
||||
status = Atrac9InitDecoder(data->handle, config_data);
|
||||
if (status < 0) goto fail;
|
||||
|
||||
|
@ -150,7 +150,7 @@ void reset_atrac9(atrac9_codec_data* data) {
|
|||
data->handle = Atrac9GetHandle();
|
||||
if (!data->handle) goto fail;
|
||||
|
||||
put_32bitBE(config_data, data->config.config_data);
|
||||
put_u32be(config_data, data->config.config_data);
|
||||
status = Atrac9InitDecoder(data->handle, config_data);
|
||||
if (status < 0) goto fail;
|
||||
}
|
||||
|
|
|
@ -330,7 +330,7 @@ circus_handle_t* circus_init(off_t start, uint8_t codec, uint8_t flags) {
|
|||
handle->flags = flags; //(config >> 8) & 0xFF;
|
||||
|
||||
scale_index = (handle->flags & 0xF);
|
||||
if (scale_index > 5) goto fail;
|
||||
if (scale_index >= 5) goto fail;
|
||||
handle->scales = scale_table[scale_index];
|
||||
|
||||
if (handle->codec == XPCM_CODEC_VQ_DEFLATE) {
|
||||
|
|
|
@ -45,6 +45,7 @@ void decode_ubi_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspaci
|
|||
void decode_ubi_sce_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
void decode_h4m_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, uint16_t frame_format);
|
||||
void decode_cd_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_crankcase_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
size_t ima_bytes_to_samples(size_t bytes, int channels);
|
||||
size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels);
|
||||
size_t xbox_ima_bytes_to_samples(size_t bytes, int channels);
|
||||
|
@ -305,8 +306,9 @@ STREAMFILE* compresswave_get_streamfile(compresswave_codec_data* data);
|
|||
/* ea_mt_decoder*/
|
||||
typedef struct ea_mt_codec_data ea_mt_codec_data;
|
||||
|
||||
ea_mt_codec_data* init_ea_mt(int channels, int type);
|
||||
ea_mt_codec_data* init_ea_mt(int channels, int pcm_blocks);
|
||||
ea_mt_codec_data* init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample, off_t* loop_offsets);
|
||||
ea_mt_codec_data* init_ea_mt_cbx(int channels);
|
||||
void decode_ea_mt(VGMSTREAM* vgmstream, sample * outbuf, int channelspacing, int32_t samples_to_do, int channel);
|
||||
void reset_ea_mt(VGMSTREAM* vgmstream);
|
||||
void flush_ea_mt(VGMSTREAM* vgmstream);
|
||||
|
|
|
@ -1,17 +1,8 @@
|
|||
#include "coding.h"
|
||||
#include "libs/utkdec.h"
|
||||
|
||||
#include "ea_mt_decoder_utk.h"
|
||||
/* Decodes EA MicroTalk */
|
||||
|
||||
/* Decodes EA MicroTalk (speech codec) using utkencode lib (slightly modified for vgmstream).
|
||||
* EA separates MT10:1 and MT5:1 (bigger frames), but apparently are the same
|
||||
* with different encoding parameters. Later revisions may have PCM blocks (rare).
|
||||
*
|
||||
* Decoder by Andrew D'Addesio: https://github.com/daddesio/utkencode
|
||||
* Info: http://wiki.niotso.org/UTK
|
||||
*/
|
||||
|
||||
|
||||
//#define UTK_MAKE_U32(a,b,c,d) ((a)|((b)<<8)|((c)<<16)|((d)<<24))
|
||||
#define UTK_ROUND(x) ((x) >= 0.0f ? ((x)+0.5f) : ((x)-0.5f))
|
||||
#define UTK_MIN(x,y) ((x)<(y)?(x):(y))
|
||||
#define UTK_MAX(x,y) ((x)>(y)?(x):(y))
|
||||
|
@ -26,21 +17,30 @@ struct ea_mt_codec_data {
|
|||
off_t loop_offset;
|
||||
int loop_sample;
|
||||
|
||||
int pcm_blocks;
|
||||
int samples_filled;
|
||||
int samples_used;
|
||||
int samples_done;
|
||||
int samples_discard;
|
||||
void* utk_context;
|
||||
void* ctx;
|
||||
};
|
||||
|
||||
static size_t ea_mt_read_callback(void *dest, int size, void *arg);
|
||||
static ea_mt_codec_data* init_ea_mt_internal(utk_type_t type, int channels, int loop_sample, off_t* loop_offsets);
|
||||
|
||||
|
||||
ea_mt_codec_data* init_ea_mt(int channels, int pcm_blocks) {
|
||||
return init_ea_mt_loops(channels, pcm_blocks, 0, NULL);
|
||||
}
|
||||
|
||||
ea_mt_codec_data* init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample, off_t *loop_offsets) {
|
||||
return init_ea_mt_internal(pcm_blocks ? UTK_EA_PCM : UTK_EA, channels, loop_sample, loop_offsets);
|
||||
}
|
||||
|
||||
ea_mt_codec_data* init_ea_mt_cbx(int channels) {
|
||||
return init_ea_mt_internal(UTK_CBX, channels, 0, NULL);
|
||||
}
|
||||
|
||||
static ea_mt_codec_data* init_ea_mt_internal(utk_type_t type, int channels, int loop_sample, off_t* loop_offsets) {
|
||||
ea_mt_codec_data* data = NULL;
|
||||
int i;
|
||||
|
||||
|
@ -48,16 +48,14 @@ ea_mt_codec_data* init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample
|
|||
if (!data) goto fail;
|
||||
|
||||
for (i = 0; i < channels; i++) {
|
||||
data[i].utk_context = calloc(1, sizeof(UTKContext));
|
||||
if (!data[i].utk_context) goto fail;
|
||||
utk_init(data[i].utk_context);
|
||||
data[i].ctx = utk_init(type);
|
||||
if (!data[i].ctx) goto fail;
|
||||
|
||||
data[i].pcm_blocks = pcm_blocks;
|
||||
data[i].loop_sample = loop_sample;
|
||||
if (loop_offsets)
|
||||
data[i].loop_offset = loop_offsets[i];
|
||||
|
||||
utk_set_callback(data[i].utk_context, data[i].buffer, UTK_BUFFER_SIZE, &data[i], &ea_mt_read_callback);
|
||||
utk_set_callback(data[i].ctx, data[i].buffer, UTK_BUFFER_SIZE, &data[i], &ea_mt_read_callback);
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -71,10 +69,9 @@ void decode_ea_mt(VGMSTREAM* vgmstream, sample_t* outbuf, int channelspacing, in
|
|||
int i;
|
||||
ea_mt_codec_data* data = vgmstream->codec_data;
|
||||
ea_mt_codec_data* ch_data = &data[channel];
|
||||
UTKContext* ctx = ch_data->utk_context;
|
||||
int samples_done = 0;
|
||||
|
||||
|
||||
float* fbuf = utk_get_samples(ch_data->ctx);
|
||||
while (samples_done < samples_to_do) {
|
||||
|
||||
if (ch_data->samples_filled) {
|
||||
|
@ -98,7 +95,7 @@ void decode_ea_mt(VGMSTREAM* vgmstream, sample_t* outbuf, int channelspacing, in
|
|||
samples_to_get = samples_to_do - samples_done;
|
||||
|
||||
for (i = ch_data->samples_used; i < ch_data->samples_used + samples_to_get; i++) {
|
||||
int pcm = UTK_ROUND(ctx->decompressed_frame[i]);
|
||||
int pcm = UTK_ROUND(fbuf[i]);
|
||||
outbuf[0] = (int16_t)UTK_CLAMP(pcm, -32768, 32767);
|
||||
outbuf += channelspacing;
|
||||
}
|
||||
|
@ -119,19 +116,20 @@ void decode_ea_mt(VGMSTREAM* vgmstream, sample_t* outbuf, int channelspacing, in
|
|||
|
||||
/* offset is usually at loop_offset here, but not always (ex. loop_sample < 432) */
|
||||
ch_data->offset = ch_data->loop_offset;
|
||||
utk_set_ptr(ctx, 0, 0); /* reset the buffer reader */
|
||||
utk_reset(ctx); /* decoder init (all fields must be reset, for some edge cases) */
|
||||
utk_set_buffer(ch_data->ctx, 0, 0); /* reset the buffer reader */
|
||||
utk_reset(ch_data->ctx); /* decoder init (all fields must be reset, for some edge cases) */
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* new frame */
|
||||
if (ch_data->pcm_blocks)
|
||||
utk_rev3_decode_frame(ctx);
|
||||
else
|
||||
utk_decode_frame(ctx);
|
||||
int samples = utk_decode_frame(ch_data->ctx);
|
||||
if (samples < 0) {
|
||||
VGM_LOG("wrong decode: %i\n", samples);
|
||||
samples = 432;
|
||||
}
|
||||
|
||||
ch_data->samples_used = 0;
|
||||
ch_data->samples_filled = 432;
|
||||
ch_data->samples_filled = samples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -143,23 +141,20 @@ static void flush_ea_mt_offsets(VGMSTREAM* vgmstream, int is_start, int samples_
|
|||
if (!data) return;
|
||||
|
||||
|
||||
/* EA-MT frames are VBR (not byte-aligned?), so utk_decoder reads new buffer data automatically.
|
||||
/* EA-MT frames are VBR and not byte-aligned, so utk_decoder reads new buffer data automatically.
|
||||
* When decoding starts or a SCHl block changes, flush_ea_mt must be called to reset the state.
|
||||
* A bit hacky but would need some restructuring otherwise. */
|
||||
|
||||
for (i = 0; i < vgmstream->channels; i++) {
|
||||
UTKContext* ctx = data[i].utk_context;
|
||||
|
||||
data[i].streamfile = vgmstream->ch[i].streamfile; /* maybe should keep its own STREAMFILE? */
|
||||
data[i].streamfile = vgmstream->ch[i].streamfile;
|
||||
if (is_start)
|
||||
data[i].offset = vgmstream->ch[i].channel_start_offset;
|
||||
else
|
||||
data[i].offset = vgmstream->ch[i].offset;
|
||||
utk_set_ptr(ctx, 0, 0); /* reset the buffer reader */
|
||||
utk_set_buffer(data[i].ctx, 0, 0); /* reset the buffer reader */
|
||||
|
||||
if (is_start) {
|
||||
utk_reset(ctx);
|
||||
ctx->parsed_header = 0;
|
||||
utk_reset(data[i].ctx);
|
||||
data[i].samples_done = 0;
|
||||
}
|
||||
|
||||
|
@ -187,7 +182,7 @@ void free_ea_mt(ea_mt_codec_data* data, int channels) {
|
|||
return;
|
||||
|
||||
for (i = 0; i < channels; i++) {
|
||||
free(data[i].utk_context);
|
||||
utk_free(data[i].ctx);
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
|
|
|
@ -1,469 +0,0 @@
|
|||
#ifndef _EA_MT_DECODER_UTK_H_
|
||||
#define _EA_MT_DECODER_UTK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Note: This struct assumes a member alignment of 4 bytes.
|
||||
** This matters when pitch_lag > 216 on the first subframe of any given frame. */
|
||||
typedef struct UTKContext {
|
||||
uint8_t *buffer;
|
||||
size_t buffer_size;
|
||||
void *arg;
|
||||
size_t (*read_callback)(void *dest, int size, void *arg);
|
||||
const uint8_t *ptr, *end;
|
||||
|
||||
int parsed_header;
|
||||
unsigned int bits_value;
|
||||
int bits_count;
|
||||
int reduced_bw;
|
||||
int multipulse_thresh;
|
||||
float fixed_gains[64];
|
||||
float rc[12];
|
||||
float synth_history[12];
|
||||
float adapt_cb[324];
|
||||
float decompressed_frame[432];
|
||||
} UTKContext;
|
||||
|
||||
enum {
|
||||
MDL_NORMAL = 0,
|
||||
MDL_LARGEPULSE = 1
|
||||
};
|
||||
|
||||
static const float utk_rc_table[64] = {
|
||||
+0.0f,
|
||||
-.99677598476409912109375f, -.99032700061798095703125f, -.983879029750823974609375f, -.977430999279022216796875f,
|
||||
-.970982015132904052734375f, -.964533984661102294921875f, -.958085000514984130859375f, -.9516370296478271484375f,
|
||||
-.930754005908966064453125f, -.904959976673126220703125f, -.879167020320892333984375f, -.853372991085052490234375f,
|
||||
-.827579021453857421875f, -.801786005496978759765625f, -.775991976261138916015625f, -.75019800662994384765625f,
|
||||
-.724404990673065185546875f, -.6986110210418701171875f, -.6706349849700927734375f, -.61904799938201904296875f,
|
||||
-.567460000514984130859375f, -.515873014926910400390625f, -.4642859995365142822265625f, -.4126980006694793701171875f,
|
||||
-.361110985279083251953125f, -.309523999691009521484375f, -.257937014102935791015625f, -.20634900033473968505859375f,
|
||||
-.1547619998455047607421875f, -.10317499935626983642578125f, -.05158700048923492431640625f,
|
||||
+0.0f,
|
||||
+.05158700048923492431640625f, +.10317499935626983642578125f, +.1547619998455047607421875f, +.20634900033473968505859375f,
|
||||
+.257937014102935791015625f, +.309523999691009521484375f, +.361110985279083251953125f, +.4126980006694793701171875f,
|
||||
+.4642859995365142822265625f, +.515873014926910400390625f, +.567460000514984130859375f, +.61904799938201904296875f,
|
||||
+.6706349849700927734375f, +.6986110210418701171875f, +.724404990673065185546875f, +.75019800662994384765625f,
|
||||
+.775991976261138916015625f, +.801786005496978759765625f, +.827579021453857421875f, +.853372991085052490234375f,
|
||||
+.879167020320892333984375f, +.904959976673126220703125f, +.930754005908966064453125f, +.9516370296478271484375f,
|
||||
+.958085000514984130859375f, +.964533984661102294921875f, +.970982015132904052734375f, +.977430999279022216796875f,
|
||||
+.983879029750823974609375f, +.99032700061798095703125f, +.99677598476409912109375f
|
||||
};
|
||||
|
||||
static const uint8_t utk_codebooks[2][256] = {
|
||||
{ /* normal model */
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 25,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 0,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 26,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 2
|
||||
}, { /* large-pulse model */
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3
|
||||
}
|
||||
};
|
||||
|
||||
static const struct {
|
||||
int next_model;
|
||||
int code_size;
|
||||
float pulse_value;
|
||||
} utk_commands[29] = {
|
||||
{MDL_LARGEPULSE, 8, 0.0f},
|
||||
{MDL_LARGEPULSE, 7, 0.0f},
|
||||
{MDL_NORMAL, 8, 0.0f},
|
||||
{MDL_NORMAL, 7, 0.0f},
|
||||
{MDL_NORMAL, 2, 0.0f},
|
||||
{MDL_NORMAL, 2, -1.0f},
|
||||
{MDL_NORMAL, 2, +1.0f},
|
||||
{MDL_NORMAL, 3, -1.0f},
|
||||
{MDL_NORMAL, 3, +1.0f},
|
||||
{MDL_LARGEPULSE, 4, -2.0f},
|
||||
{MDL_LARGEPULSE, 4, +2.0f},
|
||||
{MDL_LARGEPULSE, 3, -2.0f},
|
||||
{MDL_LARGEPULSE, 3, +2.0f},
|
||||
{MDL_LARGEPULSE, 5, -3.0f},
|
||||
{MDL_LARGEPULSE, 5, +3.0f},
|
||||
{MDL_LARGEPULSE, 4, -3.0f},
|
||||
{MDL_LARGEPULSE, 4, +3.0f},
|
||||
{MDL_LARGEPULSE, 6, -4.0f},
|
||||
{MDL_LARGEPULSE, 6, +4.0f},
|
||||
{MDL_LARGEPULSE, 5, -4.0f},
|
||||
{MDL_LARGEPULSE, 5, +4.0f},
|
||||
{MDL_LARGEPULSE, 7, -5.0f},
|
||||
{MDL_LARGEPULSE, 7, +5.0f},
|
||||
{MDL_LARGEPULSE, 6, -5.0f},
|
||||
{MDL_LARGEPULSE, 6, +5.0f},
|
||||
{MDL_LARGEPULSE, 8, -6.0f},
|
||||
{MDL_LARGEPULSE, 8, +6.0f},
|
||||
{MDL_LARGEPULSE, 7, -6.0f},
|
||||
{MDL_LARGEPULSE, 7, +6.0f}
|
||||
};
|
||||
|
||||
static int utk_read_byte(UTKContext *ctx)
|
||||
{
|
||||
if (ctx->ptr < ctx->end)
|
||||
return *ctx->ptr++;
|
||||
|
||||
if (ctx->read_callback) {
|
||||
size_t bytes_copied = ctx->read_callback(ctx->buffer, ctx->buffer_size, ctx->arg);
|
||||
if (bytes_copied > 0 && bytes_copied <= ctx->buffer_size) {
|
||||
ctx->ptr = ctx->buffer;
|
||||
ctx->end = ctx->buffer + bytes_copied;
|
||||
return *ctx->ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int16_t utk_read_i16(UTKContext *ctx)
|
||||
{
|
||||
int x = utk_read_byte(ctx);
|
||||
x = (x << 8) | utk_read_byte(ctx);
|
||||
return x;
|
||||
}
|
||||
|
||||
static int utk_read_bits(UTKContext *ctx, int count)
|
||||
{
|
||||
int ret = ctx->bits_value & ((1 << count) - 1);
|
||||
ctx->bits_value >>= count;
|
||||
ctx->bits_count -= count;
|
||||
|
||||
if (ctx->bits_count < 8) {
|
||||
/* read another byte */
|
||||
ctx->bits_value |= utk_read_byte(ctx) << ctx->bits_count;
|
||||
ctx->bits_count += 8;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void utk_parse_header(UTKContext *ctx)
|
||||
{
|
||||
int i;
|
||||
float multiplier;
|
||||
|
||||
ctx->reduced_bw = utk_read_bits(ctx, 1);
|
||||
ctx->multipulse_thresh = 32 - utk_read_bits(ctx, 4);
|
||||
ctx->fixed_gains[0] = 8.0f * (1 + utk_read_bits(ctx, 4));
|
||||
multiplier = 1.04f + utk_read_bits(ctx, 6)*0.001f;
|
||||
|
||||
for (i = 1; i < 64; i++)
|
||||
ctx->fixed_gains[i] = ctx->fixed_gains[i-1] * multiplier;
|
||||
}
|
||||
|
||||
static void utk_decode_excitation(UTKContext *ctx, int use_multipulse, float *out, int stride)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (use_multipulse) {
|
||||
/* multi-pulse model: n pulses are coded explicitly; the rest are zero */
|
||||
int model, cmd;
|
||||
model = 0;
|
||||
i = 0;
|
||||
while (i < 108) {
|
||||
cmd = utk_codebooks[model][ctx->bits_value & 0xff];
|
||||
model = utk_commands[cmd].next_model;
|
||||
utk_read_bits(ctx, utk_commands[cmd].code_size);
|
||||
|
||||
if (cmd > 3) {
|
||||
/* insert a pulse with magnitude <= 6.0f */
|
||||
out[i] = utk_commands[cmd].pulse_value;
|
||||
i += stride;
|
||||
} else if (cmd > 1) {
|
||||
/* insert between 7 and 70 zeros */
|
||||
int count = 7 + utk_read_bits(ctx, 6);
|
||||
if (i + count * stride > 108)
|
||||
count = (108 - i)/stride;
|
||||
|
||||
while (count > 0) {
|
||||
out[i] = 0.0f;
|
||||
i += stride;
|
||||
count--;
|
||||
}
|
||||
} else {
|
||||
/* insert a pulse with magnitude >= 7.0f */
|
||||
int x = 7;
|
||||
|
||||
while (utk_read_bits(ctx, 1))
|
||||
x++;
|
||||
|
||||
if (!utk_read_bits(ctx, 1))
|
||||
x *= -1;
|
||||
|
||||
out[i] = (float)x;
|
||||
i += stride;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* RELP model: entire residual (excitation) signal is coded explicitly */
|
||||
i = 0;
|
||||
while (i < 108) {
|
||||
if (!utk_read_bits(ctx, 1))
|
||||
out[i] = 0.0f;
|
||||
else if (!utk_read_bits(ctx, 1))
|
||||
out[i] = -2.0f;
|
||||
else
|
||||
out[i] = 2.0f;
|
||||
|
||||
i += stride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void rc_to_lpc(const float *rc, float *lpc)
|
||||
{
|
||||
int i, j;
|
||||
float tmp1[12];
|
||||
float tmp2[12];
|
||||
|
||||
for (i = 10; i >= 0; i--)
|
||||
tmp2[1+i] = rc[i];
|
||||
|
||||
tmp2[0] = 1.0f;
|
||||
|
||||
for (i = 0; i < 12; i++) {
|
||||
float x = -tmp2[11] * rc[11];
|
||||
|
||||
for (j = 10; j >= 0; j--) {
|
||||
x -= tmp2[j] * rc[j];
|
||||
tmp2[j+1] = x * rc[j] + tmp2[j];
|
||||
}
|
||||
|
||||
tmp1[i] = tmp2[0] = x;
|
||||
|
||||
for (j = 0; j < i; j++)
|
||||
x -= tmp1[i-1-j] * lpc[j];
|
||||
|
||||
lpc[i] = x;
|
||||
}
|
||||
}
|
||||
|
||||
static void utk_lp_synthesis_filter(UTKContext *ctx, int offset, int num_blocks)
|
||||
{
|
||||
int i, j, k;
|
||||
float lpc[12];
|
||||
float *ptr = &ctx->decompressed_frame[offset];
|
||||
|
||||
rc_to_lpc(ctx->rc, lpc);
|
||||
|
||||
for (i = 0; i < num_blocks; i++) {
|
||||
for (j = 0; j < 12; j++) {
|
||||
float x = *ptr;
|
||||
|
||||
for (k = 0; k < j; k++)
|
||||
x += lpc[k] * ctx->synth_history[k-j+12];
|
||||
for (; k < 12; k++)
|
||||
x += lpc[k] * ctx->synth_history[k-j];
|
||||
|
||||
ctx->synth_history[11-j] = x;
|
||||
*ptr++ = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Public functions.
|
||||
*/
|
||||
|
||||
static int utk_decode_frame(UTKContext *ctx)
|
||||
{
|
||||
int i, j;
|
||||
int use_multipulse = 0;
|
||||
float excitation[5+108+5];
|
||||
float rc_delta[12];
|
||||
|
||||
if (!ctx->bits_count) {
|
||||
ctx->bits_value = utk_read_byte(ctx);
|
||||
ctx->bits_count = 8;
|
||||
}
|
||||
|
||||
if (!ctx->parsed_header) {
|
||||
utk_parse_header(ctx);
|
||||
ctx->parsed_header = 1;
|
||||
}
|
||||
|
||||
memset(&excitation[0], 0, 5*sizeof(float));
|
||||
memset(&excitation[5+108], 0, 5*sizeof(float));
|
||||
|
||||
/* read the reflection coefficients */
|
||||
for (i = 0; i < 12; i++) {
|
||||
int idx;
|
||||
if (i == 0) {
|
||||
idx = utk_read_bits(ctx, 6);
|
||||
if (idx < ctx->multipulse_thresh)
|
||||
use_multipulse = 1;
|
||||
} else if (i < 4) {
|
||||
idx = utk_read_bits(ctx, 6);
|
||||
} else {
|
||||
idx = 16 + utk_read_bits(ctx, 5);
|
||||
}
|
||||
|
||||
rc_delta[i] = (utk_rc_table[idx] - ctx->rc[i])*0.25f;
|
||||
}
|
||||
|
||||
/* decode four subframes */
|
||||
for (i = 0; i < 4; i++) {
|
||||
int pitch_lag = utk_read_bits(ctx, 8);
|
||||
float pitch_gain = (float)utk_read_bits(ctx, 4)/15.0f;
|
||||
float fixed_gain = ctx->fixed_gains[utk_read_bits(ctx, 6)];
|
||||
|
||||
if (!ctx->reduced_bw) {
|
||||
utk_decode_excitation(ctx, use_multipulse, &excitation[5], 1);
|
||||
} else {
|
||||
/* residual (excitation) signal is encoded at reduced bandwidth */
|
||||
int align = utk_read_bits(ctx, 1);
|
||||
int zero = utk_read_bits(ctx, 1);
|
||||
|
||||
utk_decode_excitation(ctx, use_multipulse, &excitation[5+align], 2);
|
||||
|
||||
if (zero) {
|
||||
/* fill the remaining samples with zero
|
||||
** (spectrum is duplicated into high frequencies) */
|
||||
for (j = 0; j < 54; j++)
|
||||
excitation[5+(1-align)+2*j] = 0.0f;
|
||||
} else {
|
||||
/* interpolate the remaining samples
|
||||
** (spectrum is low-pass filtered) */
|
||||
float *ptr = &excitation[5+(1-align)];
|
||||
for (j = 0; j < 108; j += 2)
|
||||
ptr[j] = ptr[j-5] * 0.01803267933428287506103515625f
|
||||
- ptr[j-3] * 0.114591561257839202880859375f
|
||||
+ ptr[j-1] * 0.597385942935943603515625f
|
||||
+ ptr[j+1] * 0.597385942935943603515625f
|
||||
- ptr[j+3] * 0.114591561257839202880859375f
|
||||
+ ptr[j+5] * 0.01803267933428287506103515625f;
|
||||
|
||||
/* scale by 0.5f to give the sinc impulse response unit energy */
|
||||
fixed_gain *= 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 0; j < 108; j++)
|
||||
ctx->decompressed_frame[108*i+j] = fixed_gain * excitation[5+j]
|
||||
+ pitch_gain * ctx->adapt_cb[108*i+216-pitch_lag+j];
|
||||
}
|
||||
|
||||
for (i = 0; i < 324; i++)
|
||||
ctx->adapt_cb[i] = ctx->decompressed_frame[108+i];
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
for (j = 0; j < 12; j++)
|
||||
ctx->rc[j] += rc_delta[j];
|
||||
|
||||
utk_lp_synthesis_filter(ctx, 12*i, i < 3 ? 1 : 33);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void utk_init(UTKContext *ctx)
|
||||
{
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
}
|
||||
|
||||
static void utk_reset(UTKContext *ctx)
|
||||
{
|
||||
/* resets the internal state, leaving the external config/buffers
|
||||
* untouched (could be reset externally or using utk_set_x) */
|
||||
ctx->parsed_header = 0;
|
||||
ctx->bits_value = 0;
|
||||
ctx->bits_count = 0;
|
||||
ctx->reduced_bw = 0;
|
||||
ctx->multipulse_thresh = 0;
|
||||
memset(ctx->fixed_gains, 0, sizeof(ctx->fixed_gains));
|
||||
memset(ctx->rc, 0, sizeof(ctx->rc));
|
||||
memset(ctx->synth_history, 0, sizeof(ctx->synth_history));
|
||||
memset(ctx->adapt_cb, 0, sizeof(ctx->adapt_cb));
|
||||
memset(ctx->decompressed_frame, 0, sizeof(ctx->decompressed_frame));
|
||||
}
|
||||
|
||||
static void utk_set_callback(UTKContext *ctx, uint8_t *buffer, size_t buffer_size, void *arg, size_t (*read_callback)(void *, int , void *))
|
||||
{
|
||||
/* prepares for external reading */
|
||||
ctx->buffer = buffer;
|
||||
ctx->buffer_size = buffer_size;
|
||||
ctx->arg = arg;
|
||||
ctx->read_callback = read_callback;
|
||||
|
||||
/* reset the bit reader */
|
||||
ctx->bits_count = 0;
|
||||
}
|
||||
|
||||
static void utk_set_ptr(UTKContext *ctx, const uint8_t *ptr, const uint8_t *end)
|
||||
{
|
||||
/* sets the pointer to an external data buffer (can also be used to
|
||||
* reset the buffered data if set to ptr/end 0) */
|
||||
ctx->ptr = ptr;
|
||||
ctx->end = end;
|
||||
|
||||
/* reset the bit reader */
|
||||
ctx->bits_count = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** MicroTalk Revision 3 decoding function.
|
||||
*/
|
||||
|
||||
static int utk_rev3_decode_frame(UTKContext *ctx)
|
||||
{
|
||||
int pcm_data_present = (utk_read_byte(ctx) == 0xee);
|
||||
int i;
|
||||
|
||||
utk_decode_frame(ctx);
|
||||
|
||||
/* unread the last 8 bits and reset the bit reader */
|
||||
ctx->ptr--;
|
||||
ctx->bits_count = 0;
|
||||
|
||||
if (pcm_data_present) {
|
||||
/* Overwrite n samples at a given offset in the decoded frame with
|
||||
** raw PCM data. */
|
||||
int offset = utk_read_i16(ctx);
|
||||
int count = utk_read_i16(ctx);
|
||||
|
||||
/* sx.exe does not do any bounds checking or clamping of these two
|
||||
** fields (see 004274D1 in sx.exe v3.01.01), which means a specially
|
||||
** crafted MT5:1 file can crash sx.exe.
|
||||
** We will throw an error instead. */
|
||||
if (offset < 0 || offset > 432) {
|
||||
return -1; /* invalid PCM offset */
|
||||
}
|
||||
if (count < 0 || count > 432 - offset) {
|
||||
return -2; /* invalid PCM count */
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
ctx->decompressed_frame[offset+i] = (float)utk_read_i16(ctx);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* _EA_MT_DECODER_UTK_H_ */
|
|
@ -33,7 +33,7 @@ static const float xa_coefs[16][2] = {
|
|||
void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
uint8_t frame[0x4c] = {0};
|
||||
off_t frame_offset;
|
||||
int group, row, i, samples_done = 0, sample_count = 0;
|
||||
int samples_done = 0, sample_count = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
|
||||
|
||||
|
@ -55,11 +55,11 @@ void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspa
|
|||
|
||||
|
||||
/* parse group headers */
|
||||
for (group = 0; group < 4; group++) {
|
||||
for (int group = 0; group < 4; group++) {
|
||||
float coef1, coef2;
|
||||
int16_t hist1, hist2;
|
||||
uint8_t shift;
|
||||
uint32_t group_header = (uint32_t)get_32bitLE(frame + group*0x4); /* always LE */
|
||||
uint32_t group_header = get_u32le(frame + group*0x4); /* always LE */
|
||||
|
||||
coef1 = xa_coefs[group_header & 0x0F][0];
|
||||
coef2 = xa_coefs[group_header & 0x0F][1];
|
||||
|
@ -80,8 +80,8 @@ void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspa
|
|||
sample_count++;
|
||||
|
||||
/* process nibbles per group */
|
||||
for (row = 0; row < 15; row++) {
|
||||
for (i = 0; i < 1*2; i++) {
|
||||
for (int row = 0; row < 15; row++) {
|
||||
for (int i = 0; i < 1 * 2; i++) {
|
||||
uint8_t nibbles = frame[4*4 + row*0x04 + group + i/2];
|
||||
int sample;
|
||||
|
||||
|
@ -116,7 +116,7 @@ void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspa
|
|||
void decode_ea_xas_v0(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
uint8_t frame[0x13] = {0};
|
||||
off_t frame_offset;
|
||||
int i, frames_in, samples_done = 0, sample_count = 0;
|
||||
int frames_in, samples_done = 0, sample_count = 0;
|
||||
size_t bytes_per_frame, samples_per_frame;
|
||||
|
||||
|
||||
|
@ -129,14 +129,14 @@ void decode_ea_xas_v0(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspa
|
|||
frame_offset = stream->offset + bytes_per_frame * frames_in;
|
||||
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
|
||||
|
||||
//todo see above
|
||||
//TODO make expand function and fuse with above
|
||||
|
||||
/* process frame */
|
||||
{
|
||||
float coef1, coef2;
|
||||
int16_t hist1, hist2;
|
||||
uint8_t shift;
|
||||
uint32_t frame_header = (uint32_t)get_32bitLE(frame); /* always LE */
|
||||
uint32_t frame_header = get_u32le(frame); /* always LE */
|
||||
|
||||
coef1 = xa_coefs[frame_header & 0x0F][0];
|
||||
coef2 = xa_coefs[frame_header & 0x0F][1];
|
||||
|
@ -157,7 +157,7 @@ void decode_ea_xas_v0(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspa
|
|||
sample_count++;
|
||||
|
||||
/* process nibbles */
|
||||
for (i = 0; i < 0x0f*2; i++) {
|
||||
for (int i = 0; i < 0x0f * 2; i++) {
|
||||
uint8_t nibbles = frame[0x02 + 0x02 + i/2];
|
||||
int sample;
|
||||
|
||||
|
|
|
@ -35,8 +35,8 @@ static const int IMA_IndexTable[16] = {
|
|||
|
||||
|
||||
/* Original IMA expansion, using shift+ADDs to avoid MULs (slow back then) */
|
||||
static void std_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) {
|
||||
int sample_nibble, sample_decoded, step, delta;
|
||||
static void std_ima_expand_nibble_data(uint8_t byte, int shift, int32_t* hist1, int32_t* index) {
|
||||
int code, sample, step, delta;
|
||||
|
||||
/* simplified through math from:
|
||||
* - diff = (code + 1/2) * (step / 4)
|
||||
|
@ -44,21 +44,26 @@ static void std_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset,
|
|||
* > diff = (step * nibble / 4) + (step / 8)
|
||||
* final diff = [signed] (step / 8) + (step / 4) + (step / 2) + (step) [when code = 4+2+1] */
|
||||
|
||||
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf; /* ADPCM code */
|
||||
sample_decoded = *hist1; /* predictor value */
|
||||
step = ADPCMTable[*step_index]; /* current step */
|
||||
code = (byte >> shift) & 0xf;
|
||||
sample = *hist1; /* predictor value */
|
||||
step = ADPCMTable[*index]; /* current step */
|
||||
|
||||
delta = step >> 3;
|
||||
if (sample_nibble & 1) delta += step >> 2;
|
||||
if (sample_nibble & 2) delta += step >> 1;
|
||||
if (sample_nibble & 4) delta += step;
|
||||
if (sample_nibble & 8) delta = -delta;
|
||||
sample_decoded += delta;
|
||||
if (code & 1) delta += step >> 2;
|
||||
if (code & 2) delta += step >> 1;
|
||||
if (code & 4) delta += step;
|
||||
if (code & 8) delta = -delta;
|
||||
sample += delta;
|
||||
|
||||
*hist1 = clamp16(sample_decoded);
|
||||
*step_index += IMA_IndexTable[sample_nibble];
|
||||
if (*step_index < 0) *step_index=0;
|
||||
if (*step_index > 88) *step_index=88;
|
||||
*hist1 = clamp16(sample);
|
||||
*index += IMA_IndexTable[code];
|
||||
if (*index < 0) *index = 0;
|
||||
if (*index > 88) *index = 88;
|
||||
}
|
||||
|
||||
static void std_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) {
|
||||
uint8_t byte = read_u8(byte_offset,stream->streamfile);
|
||||
std_ima_expand_nibble_data(byte, nibble_shift, hist1, step_index);
|
||||
}
|
||||
|
||||
/* Apple's IMA variation. Exactly the same except it uses 16b history (probably more sensitive to overflow/sign extend?) */
|
||||
|
@ -1287,6 +1292,43 @@ void decode_cd_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacin
|
|||
stream->adpcm_step_index = step_index;
|
||||
}
|
||||
|
||||
/* Crankcase Audio IMA, from libs (internally CrankcaseAudio::ADPCM and revadpcm) */
|
||||
void decode_crankcase_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
uint8_t frame[0x23] = {0};
|
||||
int i, frames_in, sample_pos = 0, block_samples, frame_size;
|
||||
int32_t hist1 = stream->adpcm_history1_32;
|
||||
int step_index = stream->adpcm_step_index;
|
||||
uint32_t frame_offset;
|
||||
|
||||
/* external interleave (fixed size), mono */
|
||||
frame_size = 0x23;
|
||||
block_samples = (frame_size - 0x3) * 2;
|
||||
frames_in = first_sample / block_samples;
|
||||
first_sample = first_sample % block_samples;
|
||||
|
||||
frame_offset = stream->offset + frame_size * frames_in;
|
||||
read_streamfile(frame, frame_offset, frame_size, stream->streamfile); /* ignore EOF errors */
|
||||
|
||||
/* normal header (hist+step), mono */
|
||||
if (first_sample == 0) {
|
||||
hist1 = get_s16be(frame + 0x00);
|
||||
step_index = get_u8(frame + 0x02); /* no reserved value at 0x03 unlike other IMAs (misaligned reads?) */
|
||||
step_index = _clamp_s32(step_index, 0, 88);
|
||||
}
|
||||
|
||||
/* decode nibbles (layout: straight in mono) */
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
int pos = 0x03 + (i/2);
|
||||
int shift = (i & 1 ? 4:0); /* low first */
|
||||
|
||||
std_ima_expand_nibble_data(frame[pos], shift, &hist1, &step_index);
|
||||
outbuf[sample_pos] = (short)(hist1); /* internally output to float using "sample / 32767.0" */
|
||||
sample_pos += channelspacing;
|
||||
}
|
||||
|
||||
stream->adpcm_history1_32 = hist1;
|
||||
stream->adpcm_step_index = step_index;
|
||||
}
|
||||
|
||||
/* ************************************************************* */
|
||||
|
||||
|
|
607
Frameworks/vgmstream/vgmstream/src/coding/libs/utkdec.c
Normal file
607
Frameworks/vgmstream/vgmstream/src/coding/libs/utkdec.c
Normal file
|
@ -0,0 +1,607 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "utkdec.h"
|
||||
|
||||
|
||||
struct utk_context_t {
|
||||
/* config */
|
||||
utk_type_t type;
|
||||
int parsed_header;
|
||||
|
||||
/* state */
|
||||
struct bitreader_t {
|
||||
const uint8_t* ptr;
|
||||
uint32_t bits_value;
|
||||
int bits_count;
|
||||
/* extra (OG MT/CBX just loads ptr memory externally) */
|
||||
const uint8_t* end;
|
||||
void* arg;
|
||||
uint8_t* buffer;
|
||||
size_t buffer_size;
|
||||
size_t (*read_callback)(void* dst, int size, void* arg);
|
||||
} br;
|
||||
bool reduced_bandwidth;
|
||||
int multipulse_threshold;
|
||||
|
||||
float fixed_gains[64];
|
||||
float rc_data[12];
|
||||
float synth_history[12];
|
||||
float subframes[324 + 432];
|
||||
/* adapt_cb indexes may read from samples, join both + ptr to avoid
|
||||
* struct aligment issues (typically doesn't matter but for completeness) */
|
||||
float* adapt_cb; /* subframes + 0 */
|
||||
float* samples; /* subframes + 324 */
|
||||
};
|
||||
|
||||
|
||||
/* bit mask; (1 << count) - 1 is probably faster now but OG code uses a table */
|
||||
static const uint8_t mask_table[8] = {
|
||||
0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F,0xFF
|
||||
};
|
||||
|
||||
/* reflection coefficients, rounded that correspond to hex values in exes (actual float is longer)
|
||||
* note this table is mirrored: for (i = 1 .. 32) t[64 - i] = -t[i]) */
|
||||
static const float utk_rc_table[64] = {
|
||||
/* 6b index start */
|
||||
+0.000000f, -0.996776f, -0.990327f, -0.983879f,
|
||||
-0.977431f, -0.970982f, -0.964534f, -0.958085f,
|
||||
-0.951637f, -0.930754f, -0.904960f, -0.879167f,
|
||||
-0.853373f, -0.827579f, -0.801786f, -0.775992f,
|
||||
/* 5b index start */
|
||||
-0.750198f, -0.724405f, -0.698611f, -0.670635f,
|
||||
-0.619048f, -0.567460f, -0.515873f, -0.464286f,
|
||||
-0.412698f, -0.361111f, -0.309524f, -0.257937f,
|
||||
-0.206349f, -0.154762f, -0.103175f, -0.051587f,
|
||||
+0.000000f, +0.051587f, +0.103175f, +0.154762f,
|
||||
+0.206349f, +0.257937f, +0.309524f, +0.361111f,
|
||||
+0.412698f, +0.464286f, +0.515873f, +0.567460f,
|
||||
+0.619048f, +0.670635f, +0.698611f, +0.724405f,
|
||||
+0.750198f, +0.775992f, +0.801786f, +0.827579f,
|
||||
+0.853373f, +0.879167f, +0.904960f, +0.930754f,
|
||||
+0.951637f, +0.958085f, +0.964534f, +0.970982f,
|
||||
+0.977431f, +0.983879f, +0.990327f, +0.996776f,
|
||||
};
|
||||
|
||||
static const uint8_t utk_codebooks[2][256] = {
|
||||
/* normal model */
|
||||
{
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 25,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 0,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 21,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 26,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 17,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 22,
|
||||
4, 6, 5, 9, 4, 6, 5, 13, 4, 6, 5, 10, 4, 6, 5, 18,
|
||||
4, 6, 5, 9, 4, 6, 5, 14, 4, 6, 5, 10, 4, 6, 5, 2
|
||||
},
|
||||
/* large-pulse model */
|
||||
{
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 27,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 1,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 23,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 28,
|
||||
4, 11, 7, 15, 4, 12, 8, 19, 4, 11, 7, 16, 4, 12, 8, 24,
|
||||
4, 11, 7, 15, 4, 12, 8, 20, 4, 11, 7, 16, 4, 12, 8, 3
|
||||
},
|
||||
};
|
||||
|
||||
enum {
|
||||
MDL_NORMAL = 0,
|
||||
MDL_LARGEPULSE = 1
|
||||
};
|
||||
|
||||
static const struct {
|
||||
int next_model;
|
||||
int code_size;
|
||||
float pulse_value;
|
||||
} utk_commands[29] = {
|
||||
{MDL_LARGEPULSE, 8, 0.0f},
|
||||
{MDL_LARGEPULSE, 7, 0.0f},
|
||||
{MDL_NORMAL, 8, 0.0f},
|
||||
{MDL_NORMAL, 7, 0.0f},
|
||||
{MDL_NORMAL, 2, 0.0f},
|
||||
{MDL_NORMAL, 2, -1.0f},
|
||||
{MDL_NORMAL, 2, +1.0f},
|
||||
{MDL_NORMAL, 3, -1.0f},
|
||||
{MDL_NORMAL, 3, +1.0f},
|
||||
{MDL_LARGEPULSE, 4, -2.0f},
|
||||
{MDL_LARGEPULSE, 4, +2.0f},
|
||||
{MDL_LARGEPULSE, 3, -2.0f},
|
||||
{MDL_LARGEPULSE, 3, +2.0f},
|
||||
{MDL_LARGEPULSE, 5, -3.0f},
|
||||
{MDL_LARGEPULSE, 5, +3.0f},
|
||||
{MDL_LARGEPULSE, 4, -3.0f},
|
||||
{MDL_LARGEPULSE, 4, +3.0f},
|
||||
{MDL_LARGEPULSE, 6, -4.0f},
|
||||
{MDL_LARGEPULSE, 6, +4.0f},
|
||||
{MDL_LARGEPULSE, 5, -4.0f},
|
||||
{MDL_LARGEPULSE, 5, +4.0f},
|
||||
{MDL_LARGEPULSE, 7, -5.0f},
|
||||
{MDL_LARGEPULSE, 7, +5.0f},
|
||||
{MDL_LARGEPULSE, 6, -5.0f},
|
||||
{MDL_LARGEPULSE, 6, +5.0f},
|
||||
{MDL_LARGEPULSE, 8, -6.0f},
|
||||
{MDL_LARGEPULSE, 8, +6.0f},
|
||||
{MDL_LARGEPULSE, 7, -6.0f},
|
||||
{MDL_LARGEPULSE, 7, +6.0f}
|
||||
};
|
||||
|
||||
/* In Lego Batman 2 gain[0] = 1.068 while other games (Lego Marvel, Lego SW) is 64.0f.
|
||||
* The latter makes more sense and the former is audibly worse so it was probably a bug. */
|
||||
static const float cbx_fixed_gains[64] = {
|
||||
64.0f, 68.351997f, 72.999931f, 77.963921f,
|
||||
83.265465f, 88.927513f, 94.974579f, 101.43285f,
|
||||
108.33028f, 115.69673f, 123.5641f, 131.96646f,
|
||||
140.94017f, 150.52409f, 160.75972f, 171.69138f,
|
||||
183.36638f, 195.83528f, 209.15207f, 223.3744f,
|
||||
238.56386f, 254.78619f, 272.11163f, 290.6152f,
|
||||
310.37701f, 331.48264f, 354.02344f, 378.09702f,
|
||||
403.80759f, 431.26648f, 460.59259f, 491.91287f,
|
||||
525.36292f, 561.08759f, 599.24152f, 639.98993f,
|
||||
683.50922f, 729.98779f, 779.62695f, 832.64154f,
|
||||
889.26111f, 949.73083f, 1014.3125f, 1083.2858f,
|
||||
1156.9491f, 1235.6216f, 1319.6438f, 1409.3795f,
|
||||
1505.2173f, 1607.572f, 1716.8868f, 1833.6351f,
|
||||
1958.3223f, 2091.488f, 2233.7092f, 2385.6013f,
|
||||
2547.822f, 2721.0737f, 2906.1067f, 3103.7219f,
|
||||
3314.7749f, 3540.1794f, 3780.9116f, 4038.0134f,
|
||||
};
|
||||
|
||||
/* Bitreader in OG code can only read from set ptr; doesn't seem to check bounds though.
|
||||
* Incidentally bitreader functions seem to be used only in MT and not in other EA stuff. */
|
||||
static uint8_t read_byte(struct bitreader_t* br) {
|
||||
if (br->ptr < br->end)
|
||||
return *br->ptr++;
|
||||
|
||||
if (br->read_callback) {
|
||||
size_t bytes_copied = br->read_callback(br->buffer, br->buffer_size, br->arg);
|
||||
if (bytes_copied > 0 && bytes_copied <= br->buffer_size) {
|
||||
br->ptr = br->buffer;
|
||||
br->end = br->buffer + bytes_copied;
|
||||
return *br->ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int16_t read_s16(struct bitreader_t* br) {
|
||||
int x = read_byte(br);
|
||||
x = (x << 8) | read_byte(br);
|
||||
return x;
|
||||
}
|
||||
|
||||
static void init_bits(struct bitreader_t* br) {
|
||||
if (!br->bits_count) {
|
||||
br->bits_value = read_byte(br);
|
||||
br->bits_count = 8;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t peek_bits(struct bitreader_t* br, int count) {
|
||||
uint8_t mask = mask_table[count - 1];
|
||||
return br->bits_value & mask;
|
||||
}
|
||||
|
||||
/* assumes count <= 8, which is always true since sizes are known and don't depend on the bitstream. */
|
||||
static uint8_t read_bits(struct bitreader_t* br, int count) {
|
||||
uint8_t mask = mask_table[count - 1];
|
||||
uint8_t ret = br->bits_value & mask;
|
||||
br->bits_value >>= count;
|
||||
br->bits_count -= count;
|
||||
|
||||
if (br->bits_count < 8) {
|
||||
/* read another byte */
|
||||
br->bits_value |= read_byte(br) << br->bits_count;
|
||||
br->bits_count += 8;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* for clarity, as found in OG code (no return) */
|
||||
static void consume_bits(struct bitreader_t* br, int count) {
|
||||
read_bits(br, count);
|
||||
}
|
||||
|
||||
static void parse_header(utk_context_t* ctx) {
|
||||
if (ctx->type == UTK_CBX) {
|
||||
/* CBX uses fixed parameters unlike EA-MT, probably encoder defaults for MT10:1
|
||||
* equivalent to EA-MT with base_thre = 8, base_gain = 7, base_mult = 28 (plus rounding diffs).
|
||||
* OG CBX code uses values/tables directly rather than config though */
|
||||
ctx->reduced_bandwidth = true;
|
||||
|
||||
ctx->multipulse_threshold = 32 - 8;
|
||||
|
||||
ctx->fixed_gains[0] = cbx_fixed_gains[0];
|
||||
for (int i = 1; i < 64; i++) {
|
||||
ctx->fixed_gains[i] = cbx_fixed_gains[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
ctx->reduced_bandwidth = read_bits(&ctx->br, 1) == 1;
|
||||
|
||||
int base_thre = read_bits(&ctx->br, 4);
|
||||
int base_gain = read_bits(&ctx->br, 4);
|
||||
int base_mult = read_bits(&ctx->br, 6);
|
||||
|
||||
ctx->multipulse_threshold = 32 - base_thre;
|
||||
ctx->fixed_gains[0] = 8.0f * (1 + base_gain);
|
||||
|
||||
float multiplier = 1.04f + base_mult * 0.001f;
|
||||
for (int i = 1; i < 64; i++) {
|
||||
ctx->fixed_gains[i] = ctx->fixed_gains[i-1] * multiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void decode_excitation(utk_context_t* ctx, bool use_multipulse, float* out, int stride) {
|
||||
int i = 0;
|
||||
|
||||
if (use_multipulse) {
|
||||
/* multi-pulse model: n pulses are coded explicitly; the rest are zero */
|
||||
int model = 0;
|
||||
while (i < 108) {
|
||||
int huffman_code = peek_bits(&ctx->br, 8); /* variable-length, may consume less */
|
||||
|
||||
int cmd = utk_codebooks[model][huffman_code];
|
||||
model = utk_commands[cmd].next_model;
|
||||
|
||||
consume_bits(&ctx->br, utk_commands[cmd].code_size);
|
||||
|
||||
if (cmd > 3) {
|
||||
/* insert a pulse with magnitude <= 6.0f */
|
||||
out[i] = utk_commands[cmd].pulse_value;
|
||||
i += stride;
|
||||
}
|
||||
else if (cmd > 1) {
|
||||
/* insert between 7 and 70 zeros */
|
||||
int count = 7 + read_bits(&ctx->br, 6);
|
||||
if (i + count * stride > 108)
|
||||
count = (108 - i) / stride;
|
||||
|
||||
while (count > 0) {
|
||||
out[i] = 0.0f;
|
||||
i += stride;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* insert a pulse with magnitude >= 7.0f */
|
||||
int x = 7;
|
||||
|
||||
while (read_bits(&ctx->br, 1)) {
|
||||
x++;
|
||||
}
|
||||
|
||||
if (!read_bits(&ctx->br, 1))
|
||||
x *= -1;
|
||||
|
||||
out[i] = (float)x;
|
||||
i += stride;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* RELP model: entire residual (excitation) signal is coded explicitly */
|
||||
while (i < 108) {
|
||||
int bits = 0;
|
||||
float val = 0.0f;
|
||||
|
||||
/* peek + partial consume code (odd to use 2 codes for 0.0 but seen in multiple exes) */
|
||||
int huffman_code = peek_bits(&ctx->br, 2); /* variable-length, may consume less */
|
||||
switch (huffman_code) {
|
||||
case 0: //code: 0
|
||||
case 2: //code: 1 (maybe meant to be -0.0?)
|
||||
val = 0.0f;
|
||||
bits = 1;
|
||||
break;
|
||||
case 1: //code: 01
|
||||
val = -2.0f;
|
||||
bits = 2;
|
||||
break;
|
||||
case 3: //code: 11
|
||||
val = 2.0f;
|
||||
bits = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
consume_bits(&ctx->br, bits);
|
||||
|
||||
out[i] = val;
|
||||
i += stride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void rc_to_lpc(const float* rc_data, float* lpc) {
|
||||
int j;
|
||||
float tmp1[12];
|
||||
float tmp2[12];
|
||||
|
||||
for (int i = 10; i >= 0; i--) {
|
||||
tmp2[i + 1] = rc_data[i];
|
||||
}
|
||||
|
||||
tmp2[0] = 1.0f;
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
float x = -(rc_data[11] * tmp2[11]);
|
||||
|
||||
for (j = 10; j >= 0; j--) {
|
||||
x -= (rc_data[j] * tmp2[j]);
|
||||
tmp2[j + 1] = x * rc_data[j] + tmp2[j];
|
||||
}
|
||||
|
||||
tmp2[0] = x;
|
||||
tmp1[i] = x;
|
||||
|
||||
for (j = 0; j < i; j++) {
|
||||
x -= tmp1[i - 1 - j] * lpc[j];
|
||||
}
|
||||
|
||||
lpc[i] = x;
|
||||
}
|
||||
}
|
||||
|
||||
static void lp_synthesis_filter(utk_context_t* ctx, int offset, int blocks) {
|
||||
int i, j, k;
|
||||
float lpc[12];
|
||||
float* ptr = &ctx->samples[offset];
|
||||
|
||||
rc_to_lpc(ctx->rc_data, lpc);
|
||||
|
||||
for (i = 0; i < blocks; i++) {
|
||||
/* OG: unrolled x12*12 */
|
||||
for (j = 0; j < 12; j++) {
|
||||
float x = *ptr;
|
||||
|
||||
for (k = 0; k < j; k++) {
|
||||
x += lpc[k] * ctx->synth_history[k - j + 12];
|
||||
}
|
||||
for (; k < 12; k++) {
|
||||
x += lpc[k] * ctx->synth_history[k - j + 0];
|
||||
}
|
||||
|
||||
ctx->synth_history[11 - j] = x;
|
||||
|
||||
*ptr++ = x;
|
||||
|
||||
/* CBX only: samples are multiplied by 12582912.0, then coerce_int(sample[i]) on output
|
||||
* to get final int16, as a pseudo-optimization; not sure if worth replicating */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* OG sometimes inlines this (sx3, not B&B/CBX) */
|
||||
static void interpolate_rest(float* excitation) {
|
||||
for (int i = 0; i < 108; i += 2) {
|
||||
float tmp1 = (excitation[i - 5] + excitation[i + 5]) * 0.01803268f;
|
||||
float tmp2 = (excitation[i - 3] + excitation[i + 3]) * 0.11459156f;
|
||||
float tmp3 = (excitation[i - 1] + excitation[i + 1]) * 0.59738597f;
|
||||
excitation[i] = tmp1 - tmp2 + tmp3;
|
||||
}
|
||||
}
|
||||
|
||||
static void decode_frame_main(utk_context_t* ctx) {
|
||||
bool use_multipulse = false;
|
||||
float excitation[5 + 108 + 5]; /* extra +5*2 for interpolation */
|
||||
float rc_delta[12];
|
||||
|
||||
/* OG code usually calls this init/parse header after creation rather than on frame decode,
|
||||
* but use a flag for now since buffer can be set/reset after init */
|
||||
init_bits(&ctx->br);
|
||||
|
||||
if (!ctx->parsed_header) {
|
||||
parse_header(ctx);
|
||||
ctx->parsed_header = 1;
|
||||
}
|
||||
|
||||
|
||||
/* read the reflection coefficients (OG unrolled) */
|
||||
for (int i = 0; i < 12; i++) {
|
||||
int idx;
|
||||
if (i == 0) {
|
||||
idx = read_bits(&ctx->br, 6);
|
||||
if (idx < ctx->multipulse_threshold)
|
||||
use_multipulse = true;
|
||||
}
|
||||
else if (i < 4) {
|
||||
idx = read_bits(&ctx->br, 6);
|
||||
}
|
||||
else {
|
||||
idx = 16 + read_bits(&ctx->br, 5);
|
||||
}
|
||||
|
||||
rc_delta[i] = (utk_rc_table[idx] - ctx->rc_data[i]) * 0.25f;
|
||||
}
|
||||
|
||||
/* decode four subframes */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int pitch_lag = read_bits(&ctx->br, 8);
|
||||
int pitch_value = read_bits(&ctx->br, 4);
|
||||
int gain_index = read_bits(&ctx->br, 6);
|
||||
|
||||
float pitch_gain = (float)pitch_value / 15.0f; /* may be compiled as: value * 0.6..67 (float or double) */
|
||||
float fixed_gain = ctx->fixed_gains[gain_index];
|
||||
|
||||
if (!ctx->reduced_bandwidth) {
|
||||
/* full bandwidth (probably MT5:1) */
|
||||
decode_excitation(ctx, use_multipulse, &excitation[5 + 0], 1);
|
||||
/* OG: CBX doesn't have this flag and removes the if (so not 100% same code as MT) */
|
||||
}
|
||||
else {
|
||||
/* residual (excitation) signal is encoded at reduced bandwidth */
|
||||
int align = read_bits(&ctx->br, 1);
|
||||
int zero_flag = read_bits(&ctx->br, 1);
|
||||
|
||||
decode_excitation(ctx, use_multipulse, &excitation[5 + align], 2);
|
||||
|
||||
if (zero_flag) {
|
||||
/* fill the remaining samples with zero (spectrum is duplicated into high frequencies) */
|
||||
for (int j = 0; j < 54; j++) {
|
||||
excitation[5 + (1 - align) + 2 * j] = 0.0f;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* 0'd first + last samples for interpolation */
|
||||
memset(&excitation[0], 0, 5 * sizeof(float));
|
||||
memset(&excitation[5 + 108], 0, 5 * sizeof(float));
|
||||
|
||||
/* interpolate the remaining samples (spectrum is low-pass filtered) */
|
||||
interpolate_rest(&excitation[5 + (1 - align)]);
|
||||
|
||||
/* scale by 0.5f to give the sinc impulse response unit energy */
|
||||
fixed_gain *= 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
/* OG: sometimes unrolled */
|
||||
for (int j = 0; j < 108; j++) {
|
||||
/* This has potential to read garbage from fixed_gains/samples (-39 ~ +648). The former
|
||||
* seems avoided by the encoder but we'll clamp it just in case, while the later is common
|
||||
* and seemingly used on purpose, so it's allowed via joining adapt_cb + samples bufs. */
|
||||
int idx = 108 * i + 216 - pitch_lag + j;
|
||||
if (idx < 0) /* OG: not done but shouldn't matter */
|
||||
idx = 0;
|
||||
|
||||
float tmp1 = fixed_gain * excitation[5 + j];
|
||||
float tmp2 = pitch_gain * ctx->adapt_cb[idx];
|
||||
ctx->samples[108 * i + j] = tmp1 + tmp2;
|
||||
}
|
||||
}
|
||||
|
||||
/* OG: may be compiler-optimized to memcpy */
|
||||
for (int i = 0; i < 324; i++) {
|
||||
ctx->adapt_cb[i] = ctx->samples[108 + i];
|
||||
}
|
||||
|
||||
/* OG: unrolled x4 */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 12; j++) {
|
||||
ctx->rc_data[j] += rc_delta[j];
|
||||
}
|
||||
|
||||
int blocks = i < 3 ? 1 : 33;
|
||||
lp_synthesis_filter(ctx, 12 * i, blocks);
|
||||
}
|
||||
}
|
||||
|
||||
static int decode_frame_pcm(utk_context_t* ctx) {
|
||||
int pcm_data_present = (read_byte(&ctx->br) == 0xEE);
|
||||
int i;
|
||||
|
||||
decode_frame_main(ctx);
|
||||
|
||||
/* unread the last 8 bits and reset the bit reader
|
||||
* (a bit odd but should be safe in all cases, assuming ptr has been set) */
|
||||
ctx->br.ptr--;
|
||||
ctx->br.bits_count = 0;
|
||||
|
||||
if (pcm_data_present) {
|
||||
/* Overwrite n samples at a given offset in the decoded frame with raw PCM data. */
|
||||
int offset = read_s16(&ctx->br);
|
||||
int count = read_s16(&ctx->br);
|
||||
|
||||
/* sx.exe does not do any bounds checking or clamping of these two
|
||||
* fields (see 004274D1 in sx.exe v3.01.01), which means a specially
|
||||
* crafted MT5:1 file can crash it. We will throw an error instead. */
|
||||
if (offset < 0 || offset > 432) {
|
||||
return -1; /* invalid PCM offset */
|
||||
}
|
||||
if (count < 0 || count > 432 - offset) {
|
||||
return -2; /* invalid PCM count */
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
ctx->samples[offset+i] = (float)read_s16(&ctx->br);
|
||||
}
|
||||
}
|
||||
|
||||
return 432;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
int utk_decode_frame(utk_context_t* ctx) {
|
||||
if (ctx->type == UTK_EA_PCM) {
|
||||
return decode_frame_pcm(ctx);
|
||||
}
|
||||
else {
|
||||
decode_frame_main(ctx);
|
||||
return 432;
|
||||
}
|
||||
}
|
||||
|
||||
utk_context_t* utk_init(utk_type_t type) {
|
||||
utk_context_t* ctx = calloc(1, sizeof(utk_context_t));
|
||||
if (!ctx) return NULL;
|
||||
|
||||
//memset(ctx, 0, sizeof(*ctx));
|
||||
ctx->type = type;
|
||||
|
||||
ctx->adapt_cb = ctx->subframes + 0;
|
||||
ctx->samples = ctx->subframes + 324;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void utk_free(utk_context_t* ctx) {
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
void utk_reset(utk_context_t* ctx) {
|
||||
/* resets the internal state, leaving the external config/buffers
|
||||
* untouched (could be reset externally or using utk_set_x) */
|
||||
ctx->parsed_header = 0;
|
||||
ctx->br.bits_value = 0;
|
||||
ctx->br.bits_count = 0;
|
||||
ctx->reduced_bandwidth = 0;
|
||||
ctx->multipulse_threshold = 0;
|
||||
memset(ctx->fixed_gains, 0, sizeof(ctx->fixed_gains));
|
||||
memset(ctx->rc_data, 0, sizeof(ctx->rc_data));
|
||||
memset(ctx->synth_history, 0, sizeof(ctx->synth_history));
|
||||
memset(ctx->subframes, 0, sizeof(ctx->subframes));
|
||||
}
|
||||
|
||||
void utk_set_callback(utk_context_t* ctx, uint8_t* buffer, size_t buffer_size, void *arg, size_t (*read_callback)(void *, int , void *)) {
|
||||
ctx->br.buffer = buffer;
|
||||
ctx->br.buffer_size = buffer_size;
|
||||
ctx->br.arg = arg;
|
||||
ctx->br.read_callback = read_callback;
|
||||
|
||||
/* reset the bit reader */
|
||||
ctx->br.bits_count = 0;
|
||||
}
|
||||
|
||||
void utk_set_buffer(utk_context_t* ctx, const uint8_t* buf, size_t buf_size) {
|
||||
ctx->br.ptr = buf;
|
||||
ctx->br.end = buf + buf_size;
|
||||
|
||||
/* reset the bit reader */
|
||||
ctx->br.bits_count = 0;
|
||||
}
|
||||
|
||||
float* utk_get_samples(utk_context_t* ctx) {
|
||||
return ctx->samples;
|
||||
}
|
48
Frameworks/vgmstream/vgmstream/src/coding/libs/utkdec.h
Normal file
48
Frameworks/vgmstream/vgmstream/src/coding/libs/utkdec.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
#ifndef _UTKDEK_H_
|
||||
#define _UTKDEK_H_
|
||||
#include <stdint.h>
|
||||
|
||||
/* Decodes Electronic Arts' MicroTalk (a multipulse CELP/RELP speech codec) using utkencode lib,
|
||||
* slightly modified for vgmstream based on decompilation of EA and CBX code.
|
||||
* Original by Andrew D'Addesio: https://github.com/daddesio/utkencode (UNLICENSE/public domain)
|
||||
* Info: http://wiki.niotso.org/UTK
|
||||
*
|
||||
* EA classifies MT as MT10:1 (smaller frames) and MT5:1 (bigger frames), but both are the same
|
||||
* with different encoding parameters. Later revisions may have PCM blocks (rare). This codec was
|
||||
* also reused by Traveller Tales in CBX (same devs?) with minor modifications.
|
||||
*
|
||||
* TODO:
|
||||
* - lazy/avoid peeking/overreading when no bits left (OG code does it though, shouldn't matter)
|
||||
* - same with read_callback (doesn't affect anything but cleaner)
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
UTK_EA, // standard EA-MT (MT10 or MT5)
|
||||
UTK_EA_PCM, // EA-MT with PCM blocks
|
||||
UTK_CBX, // Traveller's Tales Chatterbox
|
||||
} utk_type_t;
|
||||
|
||||
/* opaque struct */
|
||||
typedef struct utk_context_t utk_context_t;
|
||||
|
||||
/* inits UTK (must be externally created + init here) */
|
||||
utk_context_t* utk_init(utk_type_t type);
|
||||
|
||||
void utk_free(utk_context_t*);
|
||||
|
||||
/* reset/flush */
|
||||
void utk_reset(utk_context_t* ctx);
|
||||
|
||||
/* loads current data (can also be used to reset buffered data if set to 0) */
|
||||
void utk_set_buffer(utk_context_t* ctx, const uint8_t* buf, size_t buf_size);
|
||||
|
||||
/* prepares for external streaming (buf is where reads store data, arg is any external params for the callback) */
|
||||
void utk_set_callback(utk_context_t* ctx, uint8_t* buf, size_t buf_size, void* arg, size_t (*read_callback)(void*, int , void*));
|
||||
|
||||
/* main decode; returns decoded samples on ok (always >0), < 0 on error */
|
||||
int utk_decode_frame(utk_context_t* ctx);
|
||||
|
||||
/* get sample buf (shouldn't change between calls); sample type is PCM float (+-32768 but not clamped) */
|
||||
float* utk_get_samples(utk_context_t* ctx);
|
||||
|
||||
#endif
|
|
@ -391,7 +391,7 @@ static int ealayer3_parse_frame_v2(ealayer3_buffer_t* ib, ealayer3_frame_t* eaf)
|
|||
}
|
||||
|
||||
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_common_size == 0, "EA EAL3: v2 empty frame\n"); /* seen in V2S */
|
||||
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_offset_samples > 0, "EA EAL3: v2_offset_mode=%x with value=0x%x\n", eaf->v2_offset_mode, eaf->v2_offset_samples);
|
||||
VGM_ASSERT(eaf->v2_extended_flag && eaf->v2_offset_samples > 0, "EA EAL3: v2_offset_mode=%x with value=0x%x\n", eaf->v2_offset_mode, eaf->v2_offset_samples); /* Dead Space 2 (PC) */
|
||||
//VGM_ASSERT(eaf->v2_pcm_samples > 0, "EA EAL3: v2_pcm_samples 0x%x\n", eaf->v2_pcm_samples);
|
||||
|
||||
eaf->pcm_size = (eaf->v2_pcm_samples * sizeof(int16_t) * eaf->channels);
|
||||
|
@ -668,7 +668,7 @@ static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_n
|
|||
int pos = 0;
|
||||
for (i = 0; i < pcm_number * channels_per_frame; i++) {
|
||||
int16_t pcm_sample = get_s16be(pcm_block + pos);
|
||||
put_16bitLE(outbuf + pos, pcm_sample);
|
||||
put_s16le(outbuf + pos, pcm_sample);
|
||||
|
||||
pos += sizeof(sample);
|
||||
}
|
||||
|
@ -680,7 +680,7 @@ static void ealayer3_copy_pcm_block(uint8_t* outbuf, off_t pcm_offset, int pcm_n
|
|||
int put_pos = sizeof(sample) * ch;
|
||||
for (i = 0; i < pcm_number; i++) {
|
||||
int16_t pcm_sample = get_s16be(pcm_block + get_pos);
|
||||
put_16bitLE(outbuf + put_pos, pcm_sample);
|
||||
put_s16le(outbuf + put_pos, pcm_sample);
|
||||
|
||||
get_pos += sizeof(sample);
|
||||
put_pos += sizeof(sample) * channels_per_frame;
|
||||
|
|
|
@ -135,8 +135,8 @@ static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data
|
|||
/* read + write PCM block samples (always LE) */
|
||||
for (i = 0; i < eaf->pcm_number * data->channels_per_frame; i++) {
|
||||
off_t pcm_offset = stream->offset + eaf->pre_size + eaf->mpeg_size + sizeof(sample)*i;
|
||||
int16_t pcm_sample = read_16bitLE(pcm_offset,stream->streamfile);
|
||||
put_16bitLE(ms->output_buffer + bytes_filled + sizeof(sample)*i, pcm_sample);
|
||||
int16_t pcm_sample = read_s16le(pcm_offset,stream->streamfile);
|
||||
put_s16le(ms->output_buffer + bytes_filled + sizeof(sample) * i, pcm_sample);
|
||||
}
|
||||
ms->samples_filled += eaf->pcm_number;
|
||||
|
||||
|
|
|
@ -38,6 +38,8 @@ ogg_vorbis_codec_data* init_ogg_vorbis(STREAMFILE* sf, off_t start, off_t size,
|
|||
ov_callbacks callbacks = {0};
|
||||
|
||||
//todo clean up
|
||||
if (!sf)
|
||||
return NULL;
|
||||
|
||||
callbacks.read_func = ov_read_func;
|
||||
callbacks.seek_func = ov_seek_func;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -175,8 +175,8 @@ static int build_header(uint8_t* buf, size_t bufsize, STREAMFILE* sf, off_t pack
|
|||
|
||||
if (0x07+packet_size-0x03 > bufsize) return 0;
|
||||
|
||||
put_8bit (buf+0x00, read_8bit(packet_offset,sf)); /* packet_type */
|
||||
memcpy (buf+0x01, "vorbis", 6); /* id */
|
||||
put_u8(buf+0x00, read_8bit(packet_offset,sf)); /* packet_type */
|
||||
memcpy(buf+0x01, "vorbis", 6); /* id */
|
||||
bytes = read_streamfile(buf+0x07,packet_offset+0x03, packet_size-0x03,sf); /* copy rest (all except id+"SK") */
|
||||
if (packet_size-0x03 != bytes)
|
||||
return 0;
|
||||
|
|
|
@ -740,8 +740,8 @@ static int ww2ogg_generate_vorbis_setup(bitstream_t* ow, bitstream_t* iw, vorbis
|
|||
|
||||
|
||||
/* packet header */
|
||||
put_8bit(ow->buf+0x00, 0x05); /* packet_type (setup) */
|
||||
memcpy (ow->buf+0x01, "vorbis", 6); /* id */
|
||||
put_u8(ow->buf+0x00, 0x05); /* packet_type (setup) */
|
||||
memcpy(ow->buf+0x01, "vorbis", 6); /* id */
|
||||
ow->b_off += (1+6) * 8; /* bit offset of output (Vorbis) setup, after fake type + id */
|
||||
|
||||
|
||||
|
@ -1233,12 +1233,12 @@ static int load_wvc_array(uint8_t* buf, size_t bufsize, uint32_t codebook_id, ww
|
|||
int codebook_count;
|
||||
|
||||
/* at the end of the WVC is an offset table, and we need to find codebook id (number) offset */
|
||||
table_start = get_32bitLE(wvc + wvc_size - 4); /* last offset */
|
||||
table_start = get_u32le(wvc + wvc_size - 4); /* last offset */
|
||||
codebook_count = ((wvc_size - table_start) / 4) - 1;
|
||||
if (codebook_id >= codebook_count) goto fail;
|
||||
|
||||
codebook_offset = get_32bitLE(wvc + table_start + codebook_id*4);
|
||||
codebook_size = get_32bitLE(wvc + table_start + codebook_id*4 + 4) - codebook_offset;
|
||||
codebook_offset = get_u32le(wvc + table_start + codebook_id*4);
|
||||
codebook_size = get_u32le(wvc + table_start + codebook_id*4 + 4) - codebook_offset;
|
||||
if (codebook_size > bufsize) goto fail;
|
||||
|
||||
memcpy(buf, wvc+codebook_offset, codebook_size);
|
||||
|
|
|
@ -23,7 +23,6 @@ static const char* extension_list[] = {
|
|||
|
||||
"208",
|
||||
"2dx9",
|
||||
"2pfs",
|
||||
"3do",
|
||||
"3ds", //txth/reserved [F1 2011 (3DS)]
|
||||
"4", //for Game.com audio
|
||||
|
@ -131,7 +130,9 @@ static const char* extension_list[] = {
|
|||
|
||||
"cads",
|
||||
"caf",
|
||||
"cat",
|
||||
"cbd2",
|
||||
"cbx",
|
||||
"cd",
|
||||
"cfn", //fake extension for CAF (renamed, to be removed?)
|
||||
"chd", //txth/reserved [Donkey Konga (GC), Star Fox Assault (GC)]
|
||||
|
@ -203,6 +204,7 @@ static const char* extension_list[] = {
|
|||
"gcm",
|
||||
"gcub",
|
||||
"gcw",
|
||||
"ged",
|
||||
"genh",
|
||||
"gin",
|
||||
"gmd", //txth/semi [High Voltage games: Charlie and the Chocolate Factory (GC), Zathura (GC)]
|
||||
|
@ -211,6 +213,7 @@ static const char* extension_list[] = {
|
|||
"gsf",
|
||||
"gsp",
|
||||
"gtd",
|
||||
"gwb",
|
||||
"gwm",
|
||||
|
||||
"h4m",
|
||||
|
@ -344,6 +347,7 @@ static const char* extension_list[] = {
|
|||
//"m4a", //common
|
||||
//"m4v", //common
|
||||
//"mov", //common
|
||||
"move",
|
||||
//"mp+", //common [Moonshine Runners (PC)]
|
||||
//"mp2", //common
|
||||
//"mp3", //common
|
||||
|
@ -364,6 +368,7 @@ static const char* extension_list[] = {
|
|||
"msvp", //fake extension/header id for .msv
|
||||
"mta2",
|
||||
"mtaf",
|
||||
"mtt", //txth/reserved [Splinter Cell: Pandora Tomorrow (PS2)]
|
||||
"mul",
|
||||
"mups",
|
||||
"mus",
|
||||
|
@ -380,6 +385,7 @@ static const char* extension_list[] = {
|
|||
"nds",
|
||||
"ndp", //fake extension/header id for .nds
|
||||
"nlsd",
|
||||
"no",
|
||||
"nop",
|
||||
"nps",
|
||||
"npsf", //fake extension/header id for .nps (in bigfiles)
|
||||
|
@ -392,6 +398,7 @@ static const char* extension_list[] = {
|
|||
"nwa",
|
||||
"nwav",
|
||||
"nxa",
|
||||
"nxopus",
|
||||
|
||||
//"ogg", //common
|
||||
"ogg_",
|
||||
|
@ -422,7 +429,7 @@ static const char* extension_list[] = {
|
|||
"psb",
|
||||
"psf",
|
||||
"psh", //fake extension for .vsv (to be removed)
|
||||
"psnd",
|
||||
"psn",
|
||||
"pwb",
|
||||
|
||||
"r",
|
||||
|
@ -474,6 +481,7 @@ static const char* extension_list[] = {
|
|||
"sbr",
|
||||
"sbv",
|
||||
"sig",
|
||||
"slb", //txth/reserved [Vingt-et-un Systems PS2 games (Last Escort, etc]
|
||||
"sm0",
|
||||
"sm1",
|
||||
"sm2",
|
||||
|
@ -522,6 +530,7 @@ static const char* extension_list[] = {
|
|||
"sps",
|
||||
"spsd",
|
||||
"spw",
|
||||
"srsa",
|
||||
"ss2",
|
||||
"ssd", //txth/reserved [Zack & Wiki (Wii)]
|
||||
"ssm",
|
||||
|
@ -535,6 +544,7 @@ static const char* extension_list[] = {
|
|||
"stream",
|
||||
"strm",
|
||||
"sts",
|
||||
"stv", //txth/reserved [Socio Art Logic PS2 games (Zero no Tsukaima games, Cambrian QTS, Shirogane no Soleil, etc)]
|
||||
"sts_cp3",
|
||||
"stx",
|
||||
"svag",
|
||||
|
@ -628,6 +638,7 @@ static const char* extension_list[] = {
|
|||
"wd",
|
||||
"wem",
|
||||
"wii",
|
||||
"wiive", //txth/semi [Rubik World (Wii)]
|
||||
"wic", //txth/reserved [Road Rash (SAT)-videos]
|
||||
"wip", //txth/reserved [Colin McRae DiRT (PC)]
|
||||
"wlv", //txth/reserved [ToeJam & Earl III: Mission to Earth (DC)]
|
||||
|
@ -831,6 +842,7 @@ static const coding_info coding_info_list[] = {
|
|||
{coding_UBI_SCE_IMA, "Ubisoft 4-bit SCE IMA ADPCM"},
|
||||
{coding_H4M_IMA, "Hudson HVQM4 4-bit IMA ADPCM"},
|
||||
{coding_CD_IMA, "Crystal Dynamics 4-bit IMA ADPCM"},
|
||||
{coding_CRANKCASE_IMA, "CrankcaseAudio REV 4-bit IMA ADPCM"},
|
||||
|
||||
{coding_MSADPCM, "Microsoft 4-bit ADPCM"},
|
||||
{coding_MSADPCM_int, "Microsoft 4-bit ADPCM (mono/interleave)"},
|
||||
|
@ -1061,7 +1073,7 @@ static const meta_info meta_info_list[] = {
|
|||
{meta_RSTM_ROCKSTAR, "Rockstar Games RSTM Header"},
|
||||
{meta_ACM, "InterPlay ACM Header"},
|
||||
{meta_MUS_ACM, "InterPlay MUS ACM header"},
|
||||
{meta_PS2_KCES, "Konami KCES Header"},
|
||||
{meta_VIG_KCES, "Konami .VIG Header"},
|
||||
{meta_HXD, "Tecmo HXD Header"},
|
||||
{meta_VSV, "Square Enix .vsv Header"},
|
||||
{meta_RIFF_WAVE_labl, "RIFF WAVE header (labl looping)"},
|
||||
|
@ -1093,7 +1105,7 @@ static const meta_info meta_info_list[] = {
|
|||
{meta_MUS_KROME, "Krome .MUS header"},
|
||||
{meta_WII_SNG, "SNG DSP Header"},
|
||||
{meta_RSD, "Radical RSD header"},
|
||||
{meta_DC_ASD, "ASD Header"},
|
||||
{meta_ASD_NAXAT, "Naxat .ASD header"},
|
||||
{meta_SPSD, "Sega Naomi SPSD header"},
|
||||
{meta_FFXI_BGW, "Square Enix .BGW header"},
|
||||
{meta_FFXI_SPW, "Square Enix .SPW header"},
|
||||
|
@ -1119,8 +1131,8 @@ static const meta_info meta_info_list[] = {
|
|||
{meta_MUL, "Crystal Dynamics .MUL header"},
|
||||
{meta_THP, "Nintendo THP header"},
|
||||
{meta_STS, "Alfa System .STS header"},
|
||||
{meta_PS2_P2BT, "Pop'n'Music 7 Header"},
|
||||
{meta_PS2_GBTS, "Pop'n'Music 9 Header"},
|
||||
{meta_P2BT_MOVE_VISA, "Konami P2BT/MOVE/VISA header"},
|
||||
{meta_GBTS, "Konami GBTS header"},
|
||||
{meta_NGC_DSP_IADP, "IADP Header"},
|
||||
{meta_RIFF_WAVE_MWV, "RIFF WAVE header (ctrl looping)"},
|
||||
{meta_FFCC_STR, "Final Fantasy: Crystal Chronicles STR header"},
|
||||
|
@ -1133,7 +1145,7 @@ static const meta_info meta_info_list[] = {
|
|||
{meta_ADS_MIDWAY, "Midway ADS header"},
|
||||
{meta_PS2_MCG, "Gunvari MCG Header"},
|
||||
{meta_ZSD, "Konami ZSD header"},
|
||||
{meta_REDSPARK, "RedSpark Header"},
|
||||
{meta_REDSPARK, "RedSpark header"},
|
||||
{meta_IVAUD, "Rockstar .ivaud header"},
|
||||
{meta_DSP_WII_WSD, ".WSD header"},
|
||||
{meta_WII_NDP, "Icon Games NDP header"},
|
||||
|
@ -1208,7 +1220,7 @@ static const meta_info meta_info_list[] = {
|
|||
{meta_RAW_SNDS, "PC .snds raw header"},
|
||||
{meta_PS2_WMUS, "assumed The Warriors Sony ADPCM by .wmus extension"},
|
||||
{meta_HYPERSCAN_KVAG, "Mattel Hyperscan KVAG"},
|
||||
{meta_IOS_PSND, "PSND Header"},
|
||||
{meta_PSND, "Polarbit PSND header"},
|
||||
{meta_ADP_WILDFIRE, "Wildfire ADP! header"},
|
||||
{meta_QD_ADP, "Quantic Dream .ADP header"},
|
||||
{meta_EB_SFX, "Excitebots .sfx header"},
|
||||
|
@ -1220,7 +1232,7 @@ static const meta_info meta_info_list[] = {
|
|||
{meta_MSS, "Guerilla MCSS header"},
|
||||
{meta_PS2_HSF, "Lowrider 'HSF' header"},
|
||||
{meta_IVAG, "Namco IVAG header"},
|
||||
{meta_PS2_2PFS, "Konami 2PFS header"},
|
||||
{meta_2PFS, "Konami 2PFS header"},
|
||||
{meta_UBI_CKD, "Ubisoft CKD RIFF header"},
|
||||
{meta_PS2_VBK, "PS2 VBK Header"},
|
||||
{meta_OTM, "Otomedius OTM Header"},
|
||||
|
@ -1316,7 +1328,6 @@ static const meta_info meta_info_list[] = {
|
|||
{meta_MSV, "Sony MultiStream MSV header"},
|
||||
{meta_SDF, "Beyond Reality SDF header"},
|
||||
{meta_SVG, "High Voltage SVG header"},
|
||||
{meta_VIS, "Konami VIS header"},
|
||||
{meta_VAI, "Asobo Studio .VAI header"},
|
||||
{meta_AIF_ASOBO, "Asobo Studio .AIF header"},
|
||||
{meta_AO, "AlphaOgg .AO header"},
|
||||
|
@ -1325,7 +1336,7 @@ static const meta_info meta_info_list[] = {
|
|||
{meta_XAU_KONAMI, "Konami XAU header"},
|
||||
{meta_DERF, "Xilam DERF header"},
|
||||
{meta_UTK, "Maxis UTK header"},
|
||||
{meta_NXA, "Entergram NXA header"},
|
||||
{meta_NXA1, "Entergram NXA1 header"},
|
||||
{meta_ADPCM_CAPCOM, "Capcom .ADPCM header"},
|
||||
{meta_UE4OPUS, "Epic Games UE4OPUS header"},
|
||||
{meta_XWMA, "Microsoft XWMA RIFF header"},
|
||||
|
@ -1415,6 +1426,9 @@ static const meta_info meta_info_list[] = {
|
|||
{meta_SQUEAKSTREAM, "Torus SqueakStream header"},
|
||||
{meta_SQUEAKSAMPLE, "Torus SqueakSample header"},
|
||||
{meta_SNDS, "Sony SNDS header"},
|
||||
{meta_NXOF, "Nihon Falcom FDK header"},
|
||||
{meta_GWB_GWD, "Ubisoft GWB+GWD header"},
|
||||
{meta_CBX, "Traveller's Tales CBX header"},
|
||||
};
|
||||
|
||||
void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t out_size) {
|
||||
|
|
|
@ -11,13 +11,19 @@ static size_t get_block_header_size(STREAMFILE* sf, off_t offset, size_t channel
|
|||
void block_update_awc(off_t block_offset, VGMSTREAM * vgmstream) {
|
||||
STREAMFILE* sf = vgmstream->ch[0].streamfile;
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_32bitBE : read_32bitLE;
|
||||
size_t header_size, entries, block_size, block_samples;
|
||||
size_t channel_header_size;
|
||||
size_t header_size, entries, block_size, block_samples, frame_size;
|
||||
size_t channel_header_size;
|
||||
int i;
|
||||
|
||||
/* assumed only AWC_IMA enters here, MPEG/XMA2 need special parsing as blocked layout is too limited */
|
||||
entries = read_32bit(block_offset + 0x04, sf); /* se first channel, assume all are the same */
|
||||
//block_samples = entries * (0x800-4)*2; //todo use
|
||||
/* assumes only AWC_IMA/DSP enters here, MPEG/XMA2 need special parsing as blocked layout is too limited.
|
||||
* Block header (see awc.c for a complete description):
|
||||
* - per channel: header table (size 0x18 or 0x10)
|
||||
* - per channel: seek table (32b * entries = global samples per frame in each block) (not in DSP/Vorbis)
|
||||
* - per channel: extra table (DSP only)
|
||||
* - padding (not in ATRAC9/DSP)
|
||||
*/
|
||||
|
||||
entries = read_32bit(block_offset + 0x04, sf); /* se first channel, assume all are the same (not true in MPEG/XMA) */
|
||||
block_samples = read_32bit(block_offset + 0x0c, sf);
|
||||
block_size = vgmstream->full_block_size;
|
||||
|
||||
|
@ -25,24 +31,32 @@ void block_update_awc(off_t block_offset, VGMSTREAM * vgmstream) {
|
|||
vgmstream->next_block_offset = block_offset + block_size;
|
||||
vgmstream->current_block_samples = block_samples;
|
||||
|
||||
/* starts with a header block */
|
||||
/* for each channel
|
||||
* 0x00: start entry within channel (ie. entries * ch) but may be off by +1/+2
|
||||
* 0x04: entries
|
||||
* 0x08: samples to discard in the beginning of this block (MPEG only?)
|
||||
* 0x0c: samples in channel (for MPEG/XMA2 can vary between channels)
|
||||
* (next fields don't exist in later versions for IMA)
|
||||
* 0x10: (MPEG only, empty otherwise) close to number of frames but varies a bit?
|
||||
* 0x14: (MPEG only, empty otherwise) channel usable data size (not counting padding)
|
||||
* for each channel
|
||||
* 32b * entries = global samples per frame in each block (for MPEG probably per full frame)
|
||||
*/
|
||||
switch(vgmstream->coding_type) {
|
||||
case coding_NGC_DSP:
|
||||
channel_header_size = 0x10;
|
||||
frame_size = 0x08;
|
||||
|
||||
/* coefs on every block but it's always the same */
|
||||
dsp_read_coefs_le(vgmstream, sf, block_offset + channel_header_size * vgmstream->channels + 0x10 + 0x1c + 0x00, 0x10 + 0x60);
|
||||
dsp_read_hist_le (vgmstream, sf, block_offset + channel_header_size * vgmstream->channels + 0x10 + 0x1c + 0x20, 0x10 + 0x60);
|
||||
|
||||
header_size = 0;
|
||||
header_size += channel_header_size * vgmstream->channels; /* header table */
|
||||
/* no seek table */
|
||||
header_size += 0x70 * vgmstream->channels; /* extra table */
|
||||
/* no padding */
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
channel_header_size = get_channel_header_size(sf, block_offset, vgmstream->codec_endian);
|
||||
header_size = get_block_header_size(sf, block_offset, channel_header_size, vgmstream->channels, vgmstream->codec_endian);
|
||||
frame_size = 0x800;
|
||||
break;
|
||||
}
|
||||
|
||||
channel_header_size = get_channel_header_size(sf, block_offset, vgmstream->codec_endian);
|
||||
header_size = get_block_header_size(sf, block_offset, channel_header_size, vgmstream->channels, vgmstream->codec_endian);
|
||||
for (i = 0; i < vgmstream->channels; i++) {
|
||||
vgmstream->ch[i].offset = block_offset + header_size + 0x800*entries*i;
|
||||
VGM_ASSERT(entries != read_32bit(block_offset + channel_header_size*i + 0x04, sf), "AWC: variable number of entries found at %lx\n", block_offset);
|
||||
vgmstream->ch[i].offset = block_offset + header_size + frame_size * entries * i;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,23 +2,21 @@
|
|||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* 2PFS - from Konami Games [Mahoromatic: Moetto - KiraKira Maid-San (PS2), GANTZ The Game (PS2)] */
|
||||
VGMSTREAM* init_vgmstream_ps2_2pfs(STREAMFILE *sf) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
/* 2PFS - from Konami Games [Mahoromatic: Moetto-KiraKira Maid-San (PS2), GANTZ The Game (PS2)] */
|
||||
VGMSTREAM* init_vgmstream_2pfs(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channels, version, interleave;
|
||||
int loop_start_block, loop_end_block; /* block number */
|
||||
int loop_start_block, loop_end_block;
|
||||
int loop_start_adjust, loop_end_adjust; /* loops start/end a few samples into the start/end block */
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .sap: standard
|
||||
* .2pfs: header id? (Mahoromatic) */
|
||||
if (!check_extensions(sf, "sap,2pfs"))
|
||||
goto fail;
|
||||
|
||||
if (read_u32be(0x00,sf) != 0x32504653) /* "2PFS" */
|
||||
goto fail;
|
||||
if (!is_id32be(0x00,sf, "2PFS"))
|
||||
return NULL;
|
||||
/* .sap: standard */
|
||||
if (!check_extensions(sf, "sap"))
|
||||
return NULL;
|
||||
|
||||
version = read_u16le(0x04,sf);
|
||||
if (version != 0x01 && version != 0x02) /* v1: Mahoromatic, v2: Gantz */
|
||||
|
@ -44,7 +42,7 @@ VGMSTREAM* init_vgmstream_ps2_2pfs(STREAMFILE *sf) {
|
|||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_PS2_2PFS;
|
||||
vgmstream->meta_type = meta_2PFS;
|
||||
vgmstream->num_samples = read_u32le(0x34,sf) * 28 / 16 / channels;
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
|
@ -77,7 +75,6 @@ VGMSTREAM* init_vgmstream_ps2_2pfs(STREAMFILE *sf) {
|
|||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
|
@ -92,6 +92,7 @@ fail:
|
|||
#define ACB_TABLE_BUFFER_TRACKCOMMAND 0x2000
|
||||
#define ACB_TABLE_BUFFER_SYNTH 0x4000
|
||||
#define ACB_TABLE_BUFFER_WAVEFORM 0x4000
|
||||
#define ACB_TABLE_BUFFER_WAVEFORMEXTENSIONDATA 0x1000
|
||||
|
||||
#define ACB_MAX_NAMELIST 255
|
||||
#define ACB_MAX_NAME 1024 /* even more is possible in rare cases [Senran Kagura Burst Re:Newal (PC)] */
|
||||
|
@ -159,8 +160,15 @@ typedef struct {
|
|||
uint16_t Id;
|
||||
uint16_t PortNo;
|
||||
uint8_t Streaming;
|
||||
uint8_t LoopFlag;
|
||||
uint16_t ExtensionData;
|
||||
} Waveform_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t LoopStart;
|
||||
uint32_t LoopEnd;
|
||||
} WaveformExtensionData_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
STREAMFILE* acbFile; /* original reference, don't close */
|
||||
|
@ -178,6 +186,7 @@ typedef struct {
|
|||
STREAMFILE* TrackCommandSf;
|
||||
STREAMFILE* SynthSf;
|
||||
STREAMFILE* WaveformSf;
|
||||
STREAMFILE* WaveformExtensionDataSf;
|
||||
|
||||
Cue_t* Cue;
|
||||
CueName_t* CueName;
|
||||
|
@ -188,6 +197,7 @@ typedef struct {
|
|||
TrackCommand_t* TrackCommand;
|
||||
Synth_t* Synth;
|
||||
Waveform_t* Waveform;
|
||||
WaveformExtensionData_t* WaveformExtensionData;
|
||||
|
||||
int Cue_rows;
|
||||
int CueName_rows;
|
||||
|
@ -198,6 +208,7 @@ typedef struct {
|
|||
int TrackCommand_rows;
|
||||
int Synth_rows;
|
||||
int Waveform_rows;
|
||||
int WaveformExtensionData_rows;
|
||||
|
||||
/* config */
|
||||
int is_memory;
|
||||
|
@ -208,7 +219,8 @@ typedef struct {
|
|||
int synth_depth;
|
||||
int sequence_depth;
|
||||
|
||||
/* name stuff */
|
||||
/* name/config stuff */
|
||||
int16_t waveform_index;
|
||||
int16_t cuename_index;
|
||||
const char* cuename_name;
|
||||
int awbname_count;
|
||||
|
@ -297,7 +309,7 @@ static void add_acb_name(acb_header* acb, int8_t Streaming) {
|
|||
static int preload_acb_waveform(acb_header* acb) {
|
||||
utf_context* Table = NULL;
|
||||
int* p_rows = &acb->Waveform_rows;
|
||||
int i, c_Id, c_MemoryAwbId, c_StreamAwbId, c_StreamAwbPortNo, c_Streaming;
|
||||
int i, c_Id, c_MemoryAwbId, c_StreamAwbId, c_StreamAwbPortNo, c_Streaming, c_LoopFlag, c_ExtensionData;
|
||||
|
||||
if (*p_rows)
|
||||
return 1;
|
||||
|
@ -315,6 +327,8 @@ static int preload_acb_waveform(acb_header* acb) {
|
|||
c_StreamAwbId = utf_get_column(Table, "StreamAwbId");
|
||||
c_StreamAwbPortNo = utf_get_column(Table, "StreamAwbPortNo");
|
||||
c_Streaming = utf_get_column(Table, "Streaming");
|
||||
c_LoopFlag = utf_get_column(Table, "LoopFlag");
|
||||
c_ExtensionData = utf_get_column(Table, "ExtensionData");
|
||||
|
||||
for (i = 0; i < *p_rows; i++) {
|
||||
Waveform_t* r = &acb->Waveform[i];
|
||||
|
@ -332,6 +346,10 @@ static int preload_acb_waveform(acb_header* acb) {
|
|||
r->PortNo = 0xFFFF;
|
||||
}
|
||||
utf_query_col_u8(Table, i, c_Streaming, &r->Streaming);
|
||||
utf_query_col_u8(Table, i, c_LoopFlag, &r->LoopFlag);
|
||||
|
||||
r->ExtensionData = -1;
|
||||
utf_query_col_u16(Table, i, c_ExtensionData, &r->ExtensionData); /* optional for newer/Switch acb */
|
||||
}
|
||||
|
||||
utf_close(Table);
|
||||
|
@ -346,7 +364,7 @@ static int load_acb_waveform(acb_header* acb, uint16_t Index) {
|
|||
Waveform_t* r;
|
||||
|
||||
if (!preload_acb_waveform(acb)) goto fail;
|
||||
if (Index > acb->Waveform_rows) goto fail;
|
||||
if (Index >= acb->Waveform_rows) goto fail;
|
||||
r = &acb->Waveform[Index];
|
||||
//;VGM_LOG("acb: Waveform[%i]: Id=%i, PortNo=%i, Streaming=%i\n", Index, r->Id, r->PortNo, r->Streaming);
|
||||
|
||||
|
@ -362,6 +380,9 @@ static int load_acb_waveform(acb_header* acb, uint16_t Index) {
|
|||
if ((acb->is_memory && r->Streaming == 1) || (!acb->is_memory && r->Streaming == 0))
|
||||
return 1;
|
||||
|
||||
/* save waveid <> Index translation */
|
||||
acb->waveform_index = Index;
|
||||
|
||||
/* aaand finally get name (phew) */
|
||||
add_acb_name(acb, r->Streaming);
|
||||
|
||||
|
@ -417,7 +438,7 @@ static int load_acb_synth(acb_header* acb, uint16_t Index) {
|
|||
int i, count;
|
||||
|
||||
if (!preload_acb_synth(acb)) goto fail;
|
||||
if (Index > acb->Synth_rows) goto fail;
|
||||
if (Index >= acb->Synth_rows) goto fail;
|
||||
r = &acb->Synth[Index];
|
||||
//;VGM_LOG("acb: Synth[%i]: Type=%x, ReferenceItems={%x,%x}\n", Index, r->Type, r->ReferenceItems_offset, r->ReferenceItems_size);
|
||||
|
||||
|
@ -615,7 +636,7 @@ static int load_acb_trackcommand(acb_header* acb, uint16_t Index) {
|
|||
TrackCommand_t* r;
|
||||
|
||||
if (!preload_acb_trackcommand(acb)) goto fail;
|
||||
if (Index > acb->TrackCommand_rows) goto fail;
|
||||
if (Index >= acb->TrackCommand_rows) goto fail;
|
||||
r = &acb->TrackCommand[Index];
|
||||
//;VGM_LOG("acb: TrackEvent/Command[%i]: Command={%x,%x}\n", Index, r->Command_offset, r->Command_size);
|
||||
|
||||
|
@ -669,7 +690,7 @@ static int load_acb_track(acb_header* acb, uint16_t Index) {
|
|||
Track_t* r;
|
||||
|
||||
if (!preload_acb_track(acb)) goto fail;
|
||||
if (Index > acb->Track_rows) goto fail;
|
||||
if (Index >= acb->Track_rows) goto fail;
|
||||
r = &acb->Track[Index];
|
||||
//;VGM_LOG("acb: Track[%i]: EventIndex=%i\n", Index, r->EventIndex);
|
||||
|
||||
|
@ -736,7 +757,7 @@ static int load_acb_sequence(acb_header* acb, uint16_t Index) {
|
|||
int i;
|
||||
|
||||
if (!preload_acb_sequence(acb)) goto fail;
|
||||
if (Index > acb->Sequence_rows) goto fail;
|
||||
if (Index >= acb->Sequence_rows) goto fail;
|
||||
r = &acb->Sequence[Index];
|
||||
//;VGM_LOG("acb: Sequence[%i]: NumTracks=%i, TrackIndex={%x, %x}, Type=%x\n", Index, r->NumTracks, r->TrackIndex_offset, r->TrackIndex_size, r->Type);
|
||||
|
||||
|
@ -832,7 +853,7 @@ static int load_acb_block(acb_header* acb, uint16_t Index) {
|
|||
int i;
|
||||
|
||||
if (!preload_acb_block(acb)) goto fail;
|
||||
if (Index > acb->Block_rows) goto fail;
|
||||
if (Index >= acb->Block_rows) goto fail;
|
||||
r = &acb->Block[Index];
|
||||
//;VGM_LOG("acb: Block[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, r->NumTracks, r->TrackIndex_offset, r->TrackIndex_size);
|
||||
|
||||
|
@ -900,7 +921,7 @@ static int load_acb_blocksequence(acb_header* acb, uint16_t Index) {
|
|||
int i;
|
||||
|
||||
if (!preload_acb_blocksequence(acb)) goto fail;
|
||||
if (Index > acb->BlockSequence_rows) goto fail;
|
||||
if (Index >= acb->BlockSequence_rows) goto fail;
|
||||
r = &acb->BlockSequence[Index];
|
||||
//;VGM_LOG("acb: BlockSequence[%i]: NumTracks=%i, TrackIndex={%x, %x}, NumBlocks=%i, BlockIndex={%x, %x}\n", Index, r->NumTracks, r->TrackIndex_offset,r->TrackIndex_size, r->NumBlocks, r->BlockIndex_offset, r->BlockIndex_size);
|
||||
|
||||
|
@ -977,7 +998,7 @@ static int load_acb_cue(acb_header* acb, uint16_t Index) {
|
|||
|
||||
/* read Cue[Index] */
|
||||
if (!preload_acb_cue(acb)) goto fail;
|
||||
if (Index > acb->Cue_rows) goto fail;
|
||||
if (Index >= acb->Cue_rows) goto fail;
|
||||
r = &acb->Cue[Index];
|
||||
//;VGM_LOG("acb: Cue[%i]: ReferenceType=%i, ReferenceIndex=%i\n", Index, r->ReferenceType, r->ReferenceIndex);
|
||||
|
||||
|
@ -1064,7 +1085,7 @@ static int load_acb_cuename(acb_header* acb, uint16_t Index) {
|
|||
CueName_t* r;
|
||||
|
||||
if (!preload_acb_cuename(acb)) goto fail;
|
||||
if (Index > acb->CueName_rows) goto fail;
|
||||
if (Index >= acb->CueName_rows) goto fail;
|
||||
r = &acb->CueName[Index];
|
||||
//;VGM_LOG("acb: CueName[%i]: CueIndex=%i, CueName=%s\n", Index, r->CueIndex, r->CueName);
|
||||
|
||||
|
@ -1081,6 +1102,79 @@ fail:
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int preload_acb_waveformextensiondata(acb_header* acb) {
|
||||
utf_context* Table = NULL;
|
||||
int* p_rows = &acb->WaveformExtensionData_rows;
|
||||
int i, c_LoopStart, c_LoopEnd;
|
||||
|
||||
if (*p_rows)
|
||||
return 1;
|
||||
if (!open_utf_subtable(acb, &acb->WaveformExtensionDataSf, &Table, "WaveformExtensionDataTable", p_rows, ACB_TABLE_BUFFER_WAVEFORMEXTENSIONDATA))
|
||||
goto fail;
|
||||
if (!*p_rows)
|
||||
return 1;
|
||||
//;VGM_LOG("acb: preload WaveformExtensionData=%i\n", *p_rows);
|
||||
|
||||
acb->WaveformExtensionData = malloc(*p_rows * sizeof(WaveformExtensionData_t));
|
||||
if (!acb->WaveformExtensionData) goto fail;
|
||||
|
||||
c_LoopStart = utf_get_column(Table, "LoopStart");
|
||||
c_LoopEnd = utf_get_column(Table, "LoopEnd");
|
||||
|
||||
for (i = 0; i < *p_rows; i++) {
|
||||
WaveformExtensionData_t* r = &acb->WaveformExtensionData[i];
|
||||
|
||||
utf_query_col_u32(Table, i, c_LoopStart, &r->LoopStart);
|
||||
utf_query_col_u32(Table, i, c_LoopEnd, &r->LoopEnd);
|
||||
}
|
||||
|
||||
utf_close(Table);
|
||||
return 1;
|
||||
fail:
|
||||
VGM_LOG("acb: failed WaveformExtensionData preload\n");
|
||||
utf_close(Table);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* for Switch Opus that has loop info in a separate "WaveformExtensionData" table (pointed by a field in Waveform) */
|
||||
static int load_acb_loops(acb_header* acb, VGMSTREAM* vgmstream) {
|
||||
Waveform_t* rw;
|
||||
WaveformExtensionData_t* r;
|
||||
uint16_t WaveIndex = acb->waveform_index;
|
||||
uint16_t ExtensionIndex = -1;
|
||||
|
||||
if (vgmstream->loop_flag)
|
||||
return 0;
|
||||
|
||||
/* assumes that will be init'd before while searching for names */
|
||||
if (WaveIndex < 0) goto fail;
|
||||
//if (!preload_acb_waveform(acb)) goto fail;
|
||||
if (WaveIndex >= acb->Waveform_rows) goto fail;
|
||||
rw = &acb->Waveform[WaveIndex];
|
||||
|
||||
/* 1=no loop, 2=loop, ignore others/0(default)/255 just in case */
|
||||
if (rw->LoopFlag != 2)
|
||||
return 0;
|
||||
|
||||
ExtensionIndex = rw->ExtensionData;
|
||||
if (ExtensionIndex < 0) goto fail; /* not init'd? */
|
||||
|
||||
if (!preload_acb_waveformextensiondata(acb)) goto fail;
|
||||
if (ExtensionIndex >= acb->WaveformExtensionData_rows) goto fail;
|
||||
|
||||
r = &acb->WaveformExtensionData[ExtensionIndex];
|
||||
|
||||
//;VGM_LOG("acb: WaveformExtensionData[%i]: LoopStart=%i, LoopEnd=%i\n", Index, r->LoopStart, r->LoopEnd);
|
||||
|
||||
vgmstream_force_loop(vgmstream, 1, r->LoopStart, r->LoopEnd);
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
VGM_LOG("acb: failed WaveformExtensionData %i\n", ExtensionIndex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Normally games load a .acb + .awb, and asks the .acb to play a cue by name or index.
|
||||
|
@ -1111,7 +1205,7 @@ fail:
|
|||
* per table, meaning it uses a decent chunk of memory, but having to re-read with streamfiles is much slower.
|
||||
*/
|
||||
|
||||
void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int port, int is_memory) {
|
||||
void load_acb_wave_info(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int port, int is_memory, int load_loops) {
|
||||
acb_header acb = {0};
|
||||
int i;
|
||||
|
||||
|
@ -1129,6 +1223,7 @@ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int po
|
|||
acb.target_waveid = waveid;
|
||||
acb.target_port = port;
|
||||
acb.is_memory = is_memory;
|
||||
acb.waveform_index = -1;
|
||||
|
||||
/* read all possible cue names and find which waveids are referenced by it */
|
||||
preload_acb_cuename(&acb);
|
||||
|
@ -1143,6 +1238,11 @@ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int po
|
|||
vgmstream->stream_name[STREAM_NAME_SIZE - 1] = '\0';
|
||||
}
|
||||
|
||||
/* uncommon */
|
||||
if (load_loops) {
|
||||
load_acb_loops(&acb, vgmstream);
|
||||
}
|
||||
|
||||
/* done */
|
||||
fail:
|
||||
utf_close(acb.Header);
|
||||
|
@ -1157,6 +1257,7 @@ fail:
|
|||
close_streamfile(acb.TrackCommandSf);
|
||||
close_streamfile(acb.SynthSf);
|
||||
close_streamfile(acb.WaveformSf);
|
||||
close_streamfile(acb.WaveformExtensionDataSf);
|
||||
|
||||
free(acb.CueName);
|
||||
free(acb.Cue);
|
||||
|
@ -1167,4 +1268,5 @@ fail:
|
|||
free(acb.TrackCommand);
|
||||
free(acb.Synth);
|
||||
free(acb.Waveform);
|
||||
free(acb.WaveformExtensionData);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
typedef struct {
|
||||
int total_subsongs;
|
||||
int target_subsong;
|
||||
int version;
|
||||
int file_version;
|
||||
int header_version; /* major.minor in hex */
|
||||
|
||||
uint32_t stream_offset;
|
||||
uint32_t stream_size;
|
||||
|
@ -18,7 +19,7 @@ typedef struct {
|
|||
|
||||
static int parse_adm(adm_header_t* adm, STREAMFILE* sf);
|
||||
|
||||
static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int version);
|
||||
static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int file_version);
|
||||
|
||||
/* ADM2 - Crankcase Audio REV plugin file [The Grand Tour Game (PC)] */
|
||||
VGMSTREAM* init_vgmstream_adm2(STREAMFILE* sf) {
|
||||
|
@ -38,26 +39,24 @@ VGMSTREAM* init_vgmstream_adm3(STREAMFILE* sf) {
|
|||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "ADM3"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf, "wem"))
|
||||
if (!check_extensions(sf, "wem,bnk"))
|
||||
return NULL;
|
||||
|
||||
return init_vgmstream_adm(sf, 3);
|
||||
}
|
||||
|
||||
static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int version) {
|
||||
static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int file_version) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
adm_header_t adm = {0};
|
||||
|
||||
/* ADMx are files used with the Wwise Crankaudio plugin, that simulate engine noises with
|
||||
* base internal samples and some internal RPM config (probably). Actual file seems to
|
||||
* define some combo of samples, this only plays those separate samples.
|
||||
* Decoder is basically Apple's IMA (internally just "ADPCMDecoder") but transforms to float
|
||||
* each sample during decode by multiplying by 0.000030518509 */
|
||||
* define some combo of samples, this only plays those separate samples. */
|
||||
|
||||
adm.target_subsong = sf->stream_index;
|
||||
if (adm.target_subsong == 0) adm.target_subsong = 1;
|
||||
|
||||
adm.version = version;
|
||||
adm.file_version = file_version;
|
||||
|
||||
if (!parse_adm(&adm, sf))
|
||||
goto fail;
|
||||
|
@ -73,9 +72,21 @@ static VGMSTREAM* init_vgmstream_adm(STREAMFILE* sf, int version) {
|
|||
vgmstream->num_streams = adm.total_subsongs;
|
||||
vgmstream->stream_size = adm.stream_size;
|
||||
|
||||
vgmstream->coding_type = coding_APPLE_IMA4;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x22;
|
||||
switch(adm.header_version) {
|
||||
case 0x00070000: /* The Crew Motorfest (PC) */
|
||||
vgmstream->coding_type = coding_CRANKCASE_IMA;
|
||||
//vgmstream->layout_type = layout_interleave;
|
||||
//vgmstream->interleave_block_size = 0x23;
|
||||
break;
|
||||
|
||||
default: /* The Grand Tour Game (PC) [0x00050000], MotoGP 21 (PC) [0x00060000] */
|
||||
/* Basically Apple's IMA (internally just "ADPCMDecoder") but transforms to float
|
||||
* each sample during decode by multiplying by 0.000030518509 */
|
||||
vgmstream->coding_type = coding_APPLE_IMA4;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x22;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, adm.stream_offset))
|
||||
goto fail;
|
||||
|
@ -169,12 +180,12 @@ static int parse_adm(adm_header_t* adm, STREAMFILE* sf) {
|
|||
uint32_t offset;
|
||||
|
||||
/* 0x04: null */
|
||||
/* 0x08: version? (ADM2: 0x00050000, ADM3: 0x00060000) */
|
||||
adm->header_version = read_u32le(0x08, sf); /* ADM2: 0x00050000, ADM3: 0x00060000 (older) / 0x00070000 (2023) */
|
||||
/* 0x0c: header size */
|
||||
/* 0x10: data start */
|
||||
/* rest unknown, looks mostly the same between files (some floats and stuff) */
|
||||
|
||||
switch(adm->version) {
|
||||
switch(adm->file_version) {
|
||||
case 2:
|
||||
/* low to high */
|
||||
offset = read_u32le(0x104, sf);
|
||||
|
|
|
@ -202,12 +202,15 @@ static const adxkey_info adxkey8_list[] = {
|
|||
/* Mirai Nikki: 13-ninme no Nikki Shoyuusha Re-Write (PSP) */
|
||||
{0x58a3,0x66f5,0x599f, "FDRW17th",0},
|
||||
|
||||
/* Shoujo Yoshitsune-den Ni - Toki wo Koeru Chigiri (PS2) */
|
||||
/* Shoujo Yoshitsune-den Ni: Toki wo Koeru Chigiri (PS2) */
|
||||
{0x62d7,0x483d,0x4fb7, "YOSHI2",0},
|
||||
|
||||
/* Junjou Romantica - Koi no Doki Doki Daisakusen (PS2) (Marvelous) */
|
||||
/* Junjou Romantica: Koi no Doki Doki Daisakusen (PS2) */
|
||||
{0x5827,0x612d,0x5585, "Endress-SETSUNAI!",0},
|
||||
|
||||
/* Corpse Party: Book of Shadows (PSP) */
|
||||
{0x60ad,0x5689,0x5281, "\x83\x76\x83\x89\x83\x60\x83\x69Lovers_Day",0}, // "プラチナLovers_Day" in SHIFT-JIS
|
||||
|
||||
};
|
||||
|
||||
static const adxkey_info adxkey9_list[] = {
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
#include "../coding/coding.h"
|
||||
|
||||
/* Apple Core Audio Format File - from iOS games [Vectros (iOS), Ridge Racer Accelerated (iOS)] */
|
||||
VGMSTREAM * init_vgmstream_apple_caff(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
VGMSTREAM* init_vgmstream_apple_caff(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset = 0, chunk_offset;
|
||||
size_t file_size, data_size = 0;
|
||||
int loop_flag, channel_count = 0, sample_rate = 0;
|
||||
|
@ -15,20 +15,19 @@ VGMSTREAM * init_vgmstream_apple_caff(STREAMFILE *streamFile) {
|
|||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "caf"))
|
||||
goto fail;
|
||||
if (!is_id32be(0x00,sf, "caff"))
|
||||
return NULL;
|
||||
if (read_32bitBE(0x04,sf) != 0x00010000) /* version/flags */
|
||||
return NULL;
|
||||
if (!check_extensions(sf, "caf"))
|
||||
return NULL;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) != 0x63616666) /* "caff" */
|
||||
goto fail;
|
||||
if (read_32bitBE(0x04,streamFile) != 0x00010000) /* version/flags */
|
||||
goto fail;
|
||||
|
||||
file_size = get_streamfile_size(streamFile);
|
||||
file_size = get_streamfile_size(sf);
|
||||
chunk_offset = 0x08;
|
||||
|
||||
while (chunk_offset < file_size) {
|
||||
uint32_t chunk_type = read_32bitBE(chunk_offset+0x00,streamFile);
|
||||
uint32_t chunk_size = (uint32_t)read_64bitBE(chunk_offset+0x04,streamFile);
|
||||
uint32_t chunk_type = read_u32be(chunk_offset+0x00,sf);
|
||||
uint32_t chunk_size = (uint32_t)read_u64be(chunk_offset+0x04,sf);
|
||||
chunk_offset += 0x0c;
|
||||
|
||||
switch (chunk_type) {
|
||||
|
@ -37,26 +36,26 @@ VGMSTREAM * init_vgmstream_apple_caff(STREAMFILE *streamFile) {
|
|||
found_desc = 1;
|
||||
|
||||
{
|
||||
uint64_t sample_long = (uint64_t)read_64bitBE(chunk_offset+0x00, streamFile);
|
||||
uint64_t sample_long = read_u64be(chunk_offset+0x00, sf);
|
||||
double* sample_double; /* double sample rate, double the fun */
|
||||
|
||||
sample_double = (double*)&sample_long;
|
||||
sample_rate = (int)(*sample_double);
|
||||
}
|
||||
|
||||
codec = read_32bitBE(chunk_offset+0x08, streamFile);
|
||||
codec = read_32bitBE(chunk_offset+0x08, sf);
|
||||
//codec_flags = read_32bitBE(chunk_offset+0x0c, streamFile);
|
||||
bytes_per_packet = read_32bitBE(chunk_offset+0x10, streamFile);
|
||||
samples_per_packet = read_32bitBE(chunk_offset+0x14, streamFile);
|
||||
channels_per_packet = read_32bitBE(chunk_offset+0x18, streamFile);
|
||||
bits_per_sample = read_32bitBE(chunk_offset+0x1C, streamFile);
|
||||
bytes_per_packet = read_32bitBE(chunk_offset+0x10, sf);
|
||||
samples_per_packet = read_32bitBE(chunk_offset+0x14, sf);
|
||||
channels_per_packet = read_32bitBE(chunk_offset+0x18, sf);
|
||||
bits_per_sample = read_32bitBE(chunk_offset+0x1C, sf);
|
||||
break;
|
||||
|
||||
case 0x70616b74: /* "pakt" */
|
||||
//found_pakt = 1;
|
||||
|
||||
//packets_table_size = (uint32_t)read_64bitBE(chunk_offset+0x00,streamFile); /* 0 for constant bitrate */
|
||||
valid_samples = (uint32_t)read_64bitBE(chunk_offset+0x08,streamFile);
|
||||
//packets_table_size = (uint32_t)read_u64be(chunk_offset+0x00,streamFile); /* 0 for constant bitrate */
|
||||
valid_samples = (uint32_t)read_u64be(chunk_offset+0x08,sf);
|
||||
//priming_samples = read_32bitBE(chunk_offset+0x10,streamFile); /* encoder delay samples */
|
||||
//unused_samples = read_32bitBE(chunk_offset+0x14,streamFile); /* footer samples */
|
||||
break;
|
||||
|
@ -91,8 +90,8 @@ VGMSTREAM * init_vgmstream_apple_caff(STREAMFILE *streamFile) {
|
|||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->meta_type = meta_CAFF;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
|
||||
switch(codec) {
|
||||
case 0x6C70636D: /* "lpcm" */
|
||||
|
@ -150,7 +149,7 @@ VGMSTREAM * init_vgmstream_apple_caff(STREAMFILE *streamFile) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
if (!vgmstream_open_stream(vgmstream,sf,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
|
|
61
Frameworks/vgmstream/vgmstream/src/meta/asd_naxat.c
Normal file
61
Frameworks/vgmstream/vgmstream/src/meta/asd_naxat.c
Normal file
|
@ -0,0 +1,61 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* ASD - found Naxat (Spiel/Mesa) games [Miss Moonlight (DC), Yoshia no Oka de Nekoronde... (DC)] */
|
||||
VGMSTREAM* init_vgmstream_asd_naxat(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
uint32_t start_offset;
|
||||
int loop_flag, channels, sample_rate;
|
||||
|
||||
|
||||
/* checks */
|
||||
uint32_t data_size = read_u32le(0x00, sf); /* padded and slightly less than file size */
|
||||
if (data_size == 0 || data_size >= get_streamfile_size(sf) || data_size + 0x20 + 0x10 < get_streamfile_size(sf))
|
||||
return NULL;
|
||||
if (data_size != read_u32le(0x04,sf)) /* repeated size */
|
||||
return NULL;
|
||||
/* extension of the audio bigfiles (there are N offsets to these subfiles) */
|
||||
if (!check_extensions(sf, "asd"))
|
||||
return NULL;
|
||||
|
||||
/* fmt chunk, extra checks since format is simple */
|
||||
if (read_u16le(0x08,sf) != 0x01) /* format*/
|
||||
return NULL;
|
||||
channels = read_u16le(0x0a,sf);
|
||||
sample_rate = read_s32le(0x0c,sf);
|
||||
|
||||
if (channels < 1 || channels > 2)
|
||||
return NULL;
|
||||
if (sample_rate != 22050)
|
||||
return NULL;
|
||||
if (sample_rate * channels * sizeof(int16_t) != read_u32le(0x10,sf)) /* bitrate */
|
||||
return NULL;
|
||||
/* 04: block size, bps */
|
||||
if (read_u32le(0x18,sf) != 0x00 )
|
||||
return NULL;
|
||||
if (read_u32le(0x1c,sf) != 0x00 )
|
||||
return NULL;
|
||||
|
||||
start_offset = 0x20;
|
||||
loop_flag = 0;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_ASD_NAXAT;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = pcm16_bytes_to_samples(data_size, channels);
|
||||
|
||||
vgmstream->coding_type = coding_PCM16LE;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x02;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
//typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP, CWAC, M4A } awb_type_t;
|
||||
|
||||
static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid);
|
||||
static void load_acb_info(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid, int load_loops);
|
||||
|
||||
/* AFS2/AWB (Atom Wave Bank) - CRI container of streaming audio, often together with a .acb cue sheet */
|
||||
VGMSTREAM* init_vgmstream_awb(STREAMFILE* sf) {
|
||||
|
@ -19,6 +19,7 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
|
|||
uint8_t offset_size;
|
||||
uint16_t waveid_alignment, offset_alignment, subkey;
|
||||
int waveid;
|
||||
int load_loops = 0;
|
||||
|
||||
|
||||
/* checks */
|
||||
|
@ -126,6 +127,11 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
|
|||
extension = "m4a";
|
||||
}
|
||||
#endif
|
||||
else if (read_u32be(subfile_offset + 0x00,sf) == 0x01000080) { /* (type 24=NXOpus) */
|
||||
init_vgmstream =init_vgmstream_opus_std; /* Super Mario RPG (Switch) */
|
||||
extension = "opus";
|
||||
load_loops = 1; /* loops not in Opus (rare) but in .acb */
|
||||
}
|
||||
else { /* 12=XMA? */
|
||||
vgm_logi("AWB: unknown codec (report)\n");
|
||||
goto fail;
|
||||
|
@ -144,8 +150,8 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
|
|||
vgmstream->num_streams = total_subsongs;
|
||||
}
|
||||
|
||||
/* try to load cue names */
|
||||
load_awb_name(sf, sf_acb, vgmstream, waveid);
|
||||
/* try to load cue names+etc */
|
||||
load_acb_info(sf, sf_acb, vgmstream, waveid, load_loops);
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
|
@ -157,7 +163,7 @@ fail:
|
|||
}
|
||||
|
||||
|
||||
static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid) {
|
||||
static void load_acb_info(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid, int load_loops) {
|
||||
int is_memory = (sf_acb != NULL);
|
||||
int port = 0;
|
||||
|
||||
|
@ -170,7 +176,7 @@ static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstre
|
|||
/* try parsing TXTM if present */
|
||||
sf_acb = read_filemap_file_pos(sf, 0, &port);
|
||||
|
||||
/* try (name).awb + (name).awb */
|
||||
/* try (name).awb + (name).acb */
|
||||
if (!sf_acb) {
|
||||
sf_acb = open_streamfile_by_ext(sf, "acb");
|
||||
}
|
||||
|
@ -204,11 +210,11 @@ static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstre
|
|||
}
|
||||
|
||||
/* probably loaded */
|
||||
load_acb_wave_name(sf_acb, vgmstream, waveid, port, is_memory);
|
||||
load_acb_wave_info(sf_acb, vgmstream, waveid, port, is_memory, load_loops);
|
||||
|
||||
close_streamfile(sf_acb);
|
||||
}
|
||||
else {
|
||||
load_acb_wave_name(sf_acb, vgmstream, waveid, port, is_memory);
|
||||
load_acb_wave_info(sf_acb, vgmstream, waveid, port, is_memory, load_loops);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,36 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "awc_xma_streamfile.h"
|
||||
#include "../util/endianness.h"
|
||||
#include "awc_streamfile.h"
|
||||
#include "awc_decryption_streamfile.h"
|
||||
|
||||
typedef struct {
|
||||
int big_endian;
|
||||
int is_encrypted;
|
||||
int is_music;
|
||||
int is_streamed; /* implicit: streams=music, sfx=memory */
|
||||
|
||||
int total_subsongs;
|
||||
|
||||
int channels;
|
||||
int sample_rate;
|
||||
int codec;
|
||||
int num_samples;
|
||||
uint8_t codec;
|
||||
|
||||
int block_count;
|
||||
int block_chunk;
|
||||
|
||||
off_t stream_offset;
|
||||
size_t stream_size;
|
||||
off_t vorbis_offset[VGMSTREAM_MAX_CHANNELS];
|
||||
uint32_t tags_offset;
|
||||
uint32_t stream_offset;
|
||||
uint32_t stream_size;
|
||||
uint32_t vorbis_offset[AWC_MAX_MUSIC_CHANNELS];
|
||||
|
||||
/* stream+music only */
|
||||
uint32_t channel_hash[AWC_MAX_MUSIC_CHANNELS];
|
||||
struct {
|
||||
uint32_t hash_id;
|
||||
int tag_count;
|
||||
} stream_info[AWC_MAX_MUSIC_CHANNELS];
|
||||
} awc_header;
|
||||
|
||||
static int parse_awc_header(STREAMFILE* sf, awc_header* awc);
|
||||
|
@ -28,17 +38,30 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc);
|
|||
static layered_layout_data* build_layered_awc(STREAMFILE* sf, awc_header* awc);
|
||||
|
||||
|
||||
/* AWC - from RAGE (Rockstar Advanced Game Engine) audio [Red Dead Redemption, Max Payne 3, GTA5 (multi)] */
|
||||
/* AWC - Audio Wave Container from RAGE (Rockstar Advanced Game Engine) [GTA5 (multi), Red Dead Redemption (multi), Max Payne 3 (multi)] */
|
||||
VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* sf_body = NULL;
|
||||
awc_header awc = {0};
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf,"awc"))
|
||||
goto fail;
|
||||
if (!parse_awc_header(sf, &awc))
|
||||
goto fail;
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"awc"))
|
||||
return NULL;
|
||||
|
||||
if (awc.is_encrypted) {
|
||||
/* seen in GTA5 PC, music or sfx (not all files) */
|
||||
sf_body = setup_awcd_streamfile(sf, awc.stream_offset, awc.stream_size, awc.block_chunk);
|
||||
if (!sf_body) {
|
||||
vgm_logi("AWC: encrypted data found, needs .awckey\n");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else {
|
||||
sf_body = sf;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
|
@ -55,7 +78,7 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
|
|||
switch(awc.codec) {
|
||||
case 0x00: /* PCM (PC) sfx, very rare, lower sample rates? [Max Payne 3 (PC)] */
|
||||
case 0x01: /* PCM (PC/PS3) sfx, rarely */
|
||||
if (awc.is_music) goto fail; /* blocked_awc needs to be prepared */
|
||||
if (awc.is_streamed) goto fail; /* blocked_awc needs to be prepared */
|
||||
vgmstream->coding_type = awc.big_endian ? coding_PCM16BE : coding_PCM16LE;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x02;
|
||||
|
@ -63,75 +86,33 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
|
|||
|
||||
case 0x04: /* IMA (PC) */
|
||||
vgmstream->coding_type = coding_AWC_IMA;
|
||||
vgmstream->layout_type = awc.is_music ? layout_blocked_awc : layout_none;
|
||||
vgmstream->layout_type = awc.is_streamed ? layout_blocked_awc : layout_none;
|
||||
vgmstream->full_block_size = awc.block_chunk;
|
||||
vgmstream->codec_endian = awc.big_endian;
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case 0x05: { /* XMA2 (X360) */
|
||||
uint32_t substream_size, substream_offset;
|
||||
|
||||
if (awc.is_music) {
|
||||
/* 1ch XMAs in blocks, we'll use layered layout + custom IO to get multi-FFmpegs working */
|
||||
int i;
|
||||
layered_layout_data * data = NULL;
|
||||
|
||||
/* init layout */
|
||||
data = init_layout_layered(awc.channels);
|
||||
if (!data) goto fail;
|
||||
vgmstream->layout_data = data;
|
||||
if (awc.is_streamed) {
|
||||
vgmstream->layout_data = build_layered_awc(sf_body, &awc);
|
||||
if (!vgmstream->layout_data) goto fail;
|
||||
vgmstream->layout_type = layout_layered;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
|
||||
/* open each layer subfile */
|
||||
for (i = 0; i < awc.channels; i++) {
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
int layer_channels = 1;
|
||||
|
||||
/* build the layer VGMSTREAM */
|
||||
data->layers[i] = allocate_vgmstream(layer_channels, 0);
|
||||
if (!data->layers[i]) goto fail;
|
||||
|
||||
data->layers[i]->meta_type = meta_AWC;
|
||||
data->layers[i]->coding_type = coding_FFmpeg;
|
||||
data->layers[i]->layout_type = layout_none;
|
||||
data->layers[i]->sample_rate = awc.sample_rate;
|
||||
data->layers[i]->num_samples = awc.num_samples;
|
||||
|
||||
/* setup custom IO streamfile, pass to FFmpeg and hope it's fooled */
|
||||
temp_sf = setup_awc_xma_streamfile(sf, awc.stream_offset, awc.stream_size, awc.block_chunk, awc.channels, i);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
substream_offset = 0x00; /* where FFmpeg thinks data starts, which our custom sf will clamp */
|
||||
substream_size = get_streamfile_size(temp_sf); /* data of one XMA substream without blocks */
|
||||
|
||||
data->layers[i]->codec_data = init_ffmpeg_xma2_raw(temp_sf, substream_offset, substream_size, awc.num_samples, layer_channels, awc.sample_rate, 0, 0);
|
||||
if (data->layers[i])
|
||||
xma_fix_raw_samples(data->layers[i], temp_sf, substream_offset, substream_size, 0, 0,0); /* samples are ok? */
|
||||
close_streamfile(temp_sf);
|
||||
if (!data->layers[i]->codec_data) goto fail;
|
||||
}
|
||||
|
||||
/* setup layered VGMSTREAMs */
|
||||
if (!setup_layout_layered(data))
|
||||
goto fail;
|
||||
}
|
||||
else {
|
||||
/* regular XMA for sfx */
|
||||
vgmstream->codec_data = init_ffmpeg_xma2_raw(sf, awc.stream_offset, awc.stream_size, awc.num_samples, awc.channels, awc.sample_rate, 0, 0);
|
||||
vgmstream->codec_data = init_ffmpeg_xma2_raw(sf_body, awc.stream_offset, awc.stream_size, awc.num_samples, awc.channels, awc.sample_rate, 0, 0);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
xma_fix_raw_samples(vgmstream, sf, awc.stream_offset,awc.stream_size, 0, 0,0); /* samples are ok? */
|
||||
xma_fix_raw_samples(vgmstream, sf_body, awc.stream_offset,awc.stream_size, 0, 0,0); /* samples are ok? */
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
case 0x07: { /* MPEG (PS3) */
|
||||
mpeg_custom_config cfg = {0};
|
||||
|
@ -139,17 +120,18 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
|
|||
cfg.chunk_size = awc.block_chunk;
|
||||
cfg.big_endian = awc.big_endian;
|
||||
|
||||
vgmstream->codec_data = init_mpeg_custom(sf, awc.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_AWC, &cfg);
|
||||
vgmstream->codec_data = init_mpeg_custom(sf_body, awc.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_AWC, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
case 0x08: { /* Vorbis (PC) [Red Dead Redemption 2 (PC)] */
|
||||
if (awc.is_music) {
|
||||
vgmstream->layout_data = build_layered_awc(sf, &awc);
|
||||
case 0x08: { /* Vorbis (PC) [Red Dead Redemption 2 (PC)] */
|
||||
if (awc.is_streamed) {
|
||||
vgmstream->layout_data = build_layered_awc(sf_body, &awc);
|
||||
if (!vgmstream->layout_data) goto fail;
|
||||
vgmstream->layout_type = layout_layered;
|
||||
vgmstream->coding_type = coding_VORBIS_custom;
|
||||
|
@ -161,7 +143,7 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
|
|||
cfg.sample_rate = awc.sample_rate;
|
||||
cfg.header_offset = awc.vorbis_offset[0];
|
||||
|
||||
vgmstream->codec_data = init_vorbis_custom(sf, awc.stream_offset, VORBIS_AWC, &cfg);
|
||||
vgmstream->codec_data = init_vorbis_custom(sf_body, awc.stream_offset, VORBIS_AWC, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->coding_type = coding_VORBIS_custom;
|
||||
|
@ -169,40 +151,116 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
|
|||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
case 0x0F: { /* ATRAC9 (PC) [Red Dead Redemption (PS4)] */
|
||||
if (awc.is_streamed) {
|
||||
vgmstream->layout_data = build_layered_awc(sf_body, &awc);
|
||||
if (!vgmstream->layout_data) goto fail;
|
||||
vgmstream->layout_type = layout_layered;
|
||||
vgmstream->coding_type = coding_ATRAC9;
|
||||
}
|
||||
else {
|
||||
VGMSTREAM* temp_vs = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
|
||||
temp_sf = setup_subfile_streamfile(sf_body, awc.stream_offset, awc.stream_size, "at9");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
temp_vs = init_vgmstream_riff(temp_sf);
|
||||
close_streamfile(temp_sf);
|
||||
if (!temp_vs) goto fail;
|
||||
|
||||
temp_vs->num_streams = vgmstream->num_streams;
|
||||
temp_vs->stream_size = vgmstream->stream_size;
|
||||
temp_vs->meta_type = vgmstream->meta_type;
|
||||
strcpy(temp_vs->stream_name, vgmstream->stream_name);
|
||||
|
||||
close_vgmstream(vgmstream);
|
||||
//vgmstream = temp_vs;
|
||||
return temp_vs;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
case 0x0C: /* DSP-sfx (Switch) */
|
||||
case 0x10: /* DSP-music (Switch) */
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = awc.is_streamed ? layout_blocked_awc : layout_none;
|
||||
vgmstream->full_block_size = awc.block_chunk;
|
||||
|
||||
if (!awc.is_streamed) {
|
||||
/* dsp header */
|
||||
dsp_read_coefs_le(vgmstream, sf_body, awc.stream_offset + 0x1c + 0x00, 0x00);
|
||||
dsp_read_hist_le (vgmstream, sf_body, awc.stream_offset + 0x1c + 0x20, 0x00);
|
||||
awc.stream_offset += 0x60;
|
||||
|
||||
/* shouldn't be possible since it's only used for sfx anyway */
|
||||
if (awc.channels > 1)
|
||||
goto fail;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xFF:
|
||||
vgmstream->coding_type = coding_SILENCE;
|
||||
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "[%s]", "midi");
|
||||
break;
|
||||
|
||||
default:
|
||||
VGM_LOG("AWC: unknown codec 0x%02x\n", awc.codec);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, awc.stream_offset))
|
||||
if (!vgmstream_open_stream(vgmstream, sf_body, awc.stream_offset))
|
||||
goto fail;
|
||||
if (sf_body != sf) close_streamfile(sf_body);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
if (sf_body != sf) close_streamfile(sf_body);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* Parse Rockstar's AWC header (much info from LibertyV: https://github.com/koolkdev/libertyv).
|
||||
* Made of entries for N streams, each with a number of tags pointing to chunks (header, data, events, etc). */
|
||||
/* Parse Rockstar's AWC header (much info from LibertyV: https://github.com/koolkdev/libertyv).
|
||||
*
|
||||
* AWC defines logical streams/tracks, each with N tags (type+offset+size) that point to headers/tables with info.
|
||||
* First stream may be a "music" type, then other streams are used as channels and not always define tags.
|
||||
* When the "stream" flag is set data is divided into "blocks" (used for music), described later.
|
||||
* Streams are ordered by hash/id and its tags go in order, but data may be unordered (1st stream audio
|
||||
* or headers could go after others). Defined streams also may be unused/dummy.
|
||||
* Hashes are actually reversable and more or less stream names (see other tools).
|
||||
*
|
||||
* Rough file format:
|
||||
* - base header
|
||||
* - stream tag starts [optional]
|
||||
* - stream hash ids and tag counts (stream N has M tags)
|
||||
* - tags per stream
|
||||
* - data from tags (headers, tables, audio data, etc)
|
||||
*/
|
||||
static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
||||
uint64_t (*read_u64)(off_t,STREAMFILE*) = NULL;
|
||||
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
|
||||
uint16_t (*read_u16)(off_t,STREAMFILE*) = NULL;
|
||||
int i, ch, entries;
|
||||
uint32_t flags, info_header, tag_count = 0, tags_skip = 0;
|
||||
off_t offset;
|
||||
read_u64_t read_u64 = NULL;
|
||||
read_u32_t read_u32 = NULL;
|
||||
read_u16_t read_u16 = NULL;
|
||||
int entries;
|
||||
uint32_t flags, tag_count = 0, tags_skip = 0;
|
||||
uint32_t offset;
|
||||
int target_subsong = sf->stream_index;
|
||||
|
||||
/** base header **/
|
||||
if (is_id32be(0x00,sf,"ADAT")) {
|
||||
awc->big_endian = false;
|
||||
}
|
||||
else if (is_id32be(0x00,sf,"TADA")) {
|
||||
awc->big_endian = true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* check header */
|
||||
if (read_u32be(0x00,sf) != 0x41444154 && /* "ADAT" (LE) */
|
||||
read_u32be(0x00,sf) != 0x54414441) /* "TADA" (BE) */
|
||||
goto fail;
|
||||
|
||||
awc->big_endian = read_u32be(0x00,sf) == 0x54414441;
|
||||
if (awc->big_endian) {
|
||||
read_u64 = read_u64be;
|
||||
read_u32 = read_u32be;
|
||||
|
@ -213,74 +271,112 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
|||
read_u16 = read_u16le;
|
||||
}
|
||||
|
||||
|
||||
flags = read_u32(0x04,sf);
|
||||
entries = read_u32(0x08,sf);
|
||||
//header_size = read_u32(0x0c,sf); /* after to stream id/tags, not including chunks */
|
||||
//header_size = read_u32(0x0c,sf); /* after stream id+tags */
|
||||
|
||||
offset = 0x10;
|
||||
|
||||
/* flags = 8b (always FF) + 8b (actual flags) + 16b (version, 00=rarely, 01=common) */
|
||||
if ((flags & 0xFF00FFFF) != 0xFF000001 || (flags & 0x00F00000)) {
|
||||
VGM_LOG("AWC: unknown flags 0x%08x\n", flags);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (flags & 0x00010000) /* some kind of mini offset table */
|
||||
/* stream tag starts (ex. stream#0 = 0, stream#1 = 4, stream#2 = 7: to read tags from stream#2 skip to 7th tag) */
|
||||
if (flags & 0x00010000)
|
||||
offset += 0x2 * entries;
|
||||
//if (flags % 0x00020000) /* seems to indicate chunks are not ordered (ie. header may go after data) */
|
||||
// ...
|
||||
//if (flags % 0x00040000) /* music/multichannel flag? (GTA5, not seen in RDR) */
|
||||
// awc->is_music = 1;
|
||||
if (flags & 0x00080000) /* encrypted data chunk (most of GTA5 PC) */
|
||||
|
||||
/* seems to indicate chunks are not ordered (ie. header structures from tags may go after data), usually for non-streams */
|
||||
//if (flags % 0x00020000)
|
||||
// awc->is_unordered = 1;
|
||||
|
||||
/* stream/multichannel flag (rare, GTA5/RDR2) */
|
||||
//if (flags % 0x00040000)
|
||||
// awc->is_multichannel = 1;
|
||||
|
||||
/* encrypted data chunk (most of GTA5 PC for licensed audio) */
|
||||
if (flags & 0x00080000)
|
||||
awc->is_encrypted = 1;
|
||||
|
||||
if (awc->is_encrypted) {
|
||||
VGM_LOG("AWC: encrypted data found\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Music when the first id is 0 (base/fake entry with info for all channels), sfx pack otherwise.
|
||||
* sfx = N single streams, music = N-1 interleaved mono channels (even for MP3/XMA).
|
||||
* Music seems layered (N-1/2 stereo pairs), maybe set with events? */
|
||||
awc->is_music = (read_u32(offset + 0x00,sf) & 0x1FFFFFFF) == 0x00000000;
|
||||
if (awc->is_music) { /* all streams except id 0 is a channel */
|
||||
/* When first stream hash/id is 0 AWC it has fake entry with info for all channels = music, sfx pack otherwise.
|
||||
* sfx = N single streams, music = N interleaved mono channels (even for MP3/XMA/Vorbis/etc).
|
||||
* Channels set a stream hash/id that typically is one of the defined ones and its tags do apply to that
|
||||
* channel, but rarely may not exist. Ex.:
|
||||
*
|
||||
* - bgm01.awc
|
||||
* Stream ID 00000000 (implicit: music stream, all others aren't used)
|
||||
* Tag: music header
|
||||
* Channel 0: ID 9d66fe4c
|
||||
* Channel 1: ID 7a3837ef
|
||||
* Channel 2: ID 032c57e9 (not actually defined)
|
||||
* Tag: data chunk
|
||||
* #Tag: sfx header (only in buggy files)
|
||||
* Stream ID 7a3837ef (no tags)
|
||||
* Stream ID 9d66fe4c (notice this is channel 0 but streams are ordered by hash)
|
||||
* Tag: Event config
|
||||
*
|
||||
* - sfx01.awc
|
||||
* Stream ID 9d66fe4c
|
||||
* Tag: sfx header
|
||||
* Tag: data chunk
|
||||
* Stream ID 7a3837ef
|
||||
* Tag: sfx header
|
||||
* Tag: data chunk
|
||||
*
|
||||
* Music 'stream' defines it's own (streamed/blocked) data chunk, so other stream's data or headers aren't used,
|
||||
* but in rare cases they actually define a useless sfx header or even a separate cloned data chunk. That seems
|
||||
* to be a bug and are ignored (ex. RDR's ftr_harmonica_01, or RDR SW's countdown_song_01).
|
||||
*/
|
||||
|
||||
awc->is_streamed = (read_u32(offset + 0x00,sf) & 0x1FFFFFFF) == 0x00000000; /* first stream's hash/id is 0 */
|
||||
if (awc->is_streamed) { /* music with N channels, other streams aren't used ignored */
|
||||
awc->total_subsongs = 1;
|
||||
target_subsong = 1; /* we only need id 0, though channels may have its own tags/chunks */
|
||||
target_subsong = 1;
|
||||
/* array access below */
|
||||
if (entries >= AWC_MAX_MUSIC_CHANNELS)
|
||||
goto fail;
|
||||
}
|
||||
else { /* each stream is a single sound */
|
||||
else { /* sfx pack, each stream is a sound */
|
||||
awc->total_subsongs = entries;
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > awc->total_subsongs || awc->total_subsongs < 1) goto fail;
|
||||
}
|
||||
|
||||
|
||||
/* get stream base info */
|
||||
for (i = 0; i < entries; i++) {
|
||||
info_header = read_u32(offset + 0x04*i, sf);
|
||||
tag_count = (info_header >> 29) & 0x7; /* 3b */
|
||||
//id = (info_header >> 0) & 0x1FFFFFFF; /* 29b */
|
||||
if (target_subsong-1 == i)
|
||||
break;
|
||||
tags_skip += tag_count; /* tags to skip to reach target's tags, in the next header */
|
||||
/** stream ids and tag counts **/
|
||||
for (int i = 0; i < entries; i++) {
|
||||
uint32_t info_header = read_u32(offset + 0x00, sf);
|
||||
int entry_count = (info_header >> 29) & 0x7; /* 3b */
|
||||
uint32_t hash_id = (info_header >> 0) & 0x1FFFFFFF; /* 29b */
|
||||
|
||||
if (i + 1 < target_subsong)
|
||||
tags_skip += entry_count; /* tags to skip to reach target's tags, in the next header */
|
||||
if (target_subsong == i + 1)
|
||||
tag_count = entry_count;
|
||||
|
||||
if (awc->is_streamed) {
|
||||
awc->stream_info[i].hash_id = hash_id;
|
||||
awc->stream_info[i].tag_count = entry_count;
|
||||
}
|
||||
|
||||
offset += 0x04;
|
||||
}
|
||||
offset += 0x04*entries;
|
||||
offset += 0x08*tags_skip;
|
||||
awc->tags_offset = offset; /* where tags for stream start */
|
||||
|
||||
/* get stream tags */
|
||||
for (i = 0; i < tag_count; i++) {
|
||||
uint64_t tag_header;
|
||||
uint8_t tag_type;
|
||||
size_t tag_size;
|
||||
off_t tag_offset;
|
||||
offset += 0x08 * tags_skip; /* ignore tags for other streams */
|
||||
|
||||
tag_header = read_u64(offset + 0x08*i,sf);
|
||||
tag_type = (uint8_t)((tag_header >> 56) & 0xFF); /* 8b */
|
||||
tag_size = (size_t)((tag_header >> 28) & 0x0FFFFFFF); /* 28b */
|
||||
tag_offset = (off_t)((tag_header >> 0) & 0x0FFFFFFF); /* 28b */
|
||||
;VGM_LOG("AWC: tag%i/%i at %lx: t=%x, o=%lx, s=%x\n", i, tag_count, offset + 0x08*i, tag_type, tag_offset, tag_size);
|
||||
|
||||
/* Tags are apparently part of a hash derived from a word ("data", "format", etc).
|
||||
* If music + 1ch, the header and data chunks can repeat for no reason (sometimes not even pointed). */
|
||||
|
||||
/** tags per stream **/
|
||||
for (int i = 0; i < tag_count; i++) {
|
||||
uint64_t tag_header = read_u64(offset + 0x08*i,sf);
|
||||
uint8_t tag_type = ((tag_header >> 56) & 0xFF); /* 8b */
|
||||
uint32_t tag_size = ((tag_header >> 28) & 0x0FFFFFFF); /* 28b */
|
||||
uint32_t tag_offset = ((tag_header >> 0) & 0x0FFFFFFF); /* 28b */
|
||||
|
||||
/* types are apparently part of a hash derived from a word ("data", "format", etc). */
|
||||
switch(tag_type) {
|
||||
case 0x55: /* data */
|
||||
awc->stream_offset = tag_offset;
|
||||
|
@ -288,30 +384,31 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
|||
break;
|
||||
|
||||
case 0x48: /* music header */
|
||||
|
||||
if (!awc->is_music) {
|
||||
VGM_LOG("AWC: music header found in sfx\n");
|
||||
if (!awc->is_streamed) {
|
||||
VGM_LOG("AWC: music header found but not streamed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* 0x00(32): unknown (some count?) */
|
||||
awc->block_count = read_u32(tag_offset + 0x00,sf);
|
||||
awc->block_chunk = read_u32(tag_offset + 0x04,sf);
|
||||
awc->channels = read_u32(tag_offset + 0x08,sf);
|
||||
awc->channels = read_u32(tag_offset + 0x08,sf);
|
||||
|
||||
if (awc->channels != entries - 1) { /* not counting id-0 */
|
||||
VGM_LOG("AWC: number of music channels doesn't match entries\n");
|
||||
goto fail;
|
||||
/* extremely rare but doesn't seem to matter, some streams are dummies (RDR2 STREAMS/ABIGAIL_HUMMING_*) */
|
||||
//goto fail;
|
||||
}
|
||||
|
||||
for (ch = 0; ch < awc->channels; ch++) {
|
||||
for (int ch = 0; ch < awc->channels; ch++) {
|
||||
int num_samples, sample_rate, codec;
|
||||
/* 0x00): stream id (not always in the header entries order) */
|
||||
|
||||
awc->channel_hash[ch] = read_u32(tag_offset + 0x0c + 0x10*ch + 0x00,sf); /* reference, for vorbis */
|
||||
num_samples = read_u32(tag_offset + 0x0c + 0x10*ch + 0x04,sf);
|
||||
/* 0x08: headroom */
|
||||
sample_rate = read_u16(tag_offset + 0x0c + 0x10*ch + 0x0a,sf);
|
||||
codec = read_u8(tag_offset + 0x0c + 0x10*ch + 0x0c,sf);
|
||||
/* 0x0d(8): round size? */
|
||||
/* 0x0e: unknown (zero/-1) */
|
||||
/* 0x0e: unknown (zero/-1, loop flag? BOB_FINALE_1_A.awc, but also set in stingers) */
|
||||
|
||||
/* validate channels differences */
|
||||
if ((awc->num_samples && !(awc->num_samples >= num_samples - 10 && awc->num_samples <= num_samples + 10)) ||
|
||||
|
@ -336,77 +433,119 @@ static int parse_awc_header(STREAMFILE* sf, awc_header* awc) {
|
|||
break;
|
||||
|
||||
case 0xFA: /* sfx header */
|
||||
if (awc->is_music) {
|
||||
VGM_LOG("AWC: sfx header found in music\n");
|
||||
goto fail;
|
||||
if (awc->is_streamed) {
|
||||
VGM_LOG("AWC: sfx header found but streamed\n");
|
||||
break; //goto fail; /* rare (RDR PC/Switch) */
|
||||
}
|
||||
|
||||
awc->num_samples = read_u32(tag_offset + 0x00,sf);
|
||||
/* 0x04: -1? */
|
||||
awc->sample_rate = read_u16(tag_offset + 0x08,sf);
|
||||
/* 0x0a: unknown x4 */
|
||||
/* 0x0a: headroom */
|
||||
/* 0x0c: unknown */
|
||||
/* 0x0e: unknown */
|
||||
/* 0x10: unknown */
|
||||
/* 0x12: null? */
|
||||
awc->codec = read_u8(tag_offset + 0x13, sf);
|
||||
/* 0x14: ? (PS3 only, for any codec) */
|
||||
|
||||
awc->channels = 1;
|
||||
break;
|
||||
|
||||
case 0x76: /* sfx header for vorbis */
|
||||
if (awc->is_music) {
|
||||
VGM_LOG("AWC: sfx header found in music\n");
|
||||
if (awc->is_streamed) {
|
||||
VGM_LOG("AWC: sfx header found but streamed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
awc->num_samples = read_u32(tag_offset + 0x00,sf);
|
||||
/* 0x04: -1? */
|
||||
awc->sample_rate = read_u16(tag_offset + 0x08,sf);
|
||||
/* 0x0a: granule start? (negative) */
|
||||
/* 0x0c: granule max? */
|
||||
/* 0x0a: headroom */
|
||||
/* 0x0c: unknown */
|
||||
/* 0x0e: unknown */
|
||||
/* 0x10: unknown */
|
||||
awc->codec = read_u8(tag_offset + 0x1c, sf); /* 16b? */
|
||||
/* 0x1e: vorbis header size */
|
||||
awc->channels = 1;
|
||||
/* 0x1e: vorbis setup size */
|
||||
if (read_u16(tag_offset + 0x1e,sf))/* rarely not set and uses a tag below */
|
||||
awc->vorbis_offset[0] = tag_offset + 0x20; /* data up to vorbis setup size */
|
||||
|
||||
awc->vorbis_offset[0] = tag_offset + 0x20;
|
||||
awc->channels = 1;
|
||||
break;
|
||||
|
||||
case 0xA3: /* block-to-sample table (32b x number of blocks w/ num_samples at the start of each block) */
|
||||
case 0x7F: /* vorbis setup */
|
||||
if (awc->is_streamed) {
|
||||
/* music stream doesn't have this (instead every channel-strem have one, parsed later) */
|
||||
VGM_LOG("AWC: vorbis setup found but streamed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* very rarely used for sfx: SS_AM/GESTURE01.awc */
|
||||
awc->vorbis_offset[0] = tag_offset;
|
||||
break;
|
||||
|
||||
case 0x68: /* midi data [Red Dead Redemption 2 (PC)] */
|
||||
/* set fake info so awc doesn't break */
|
||||
awc->stream_offset = tag_offset;
|
||||
awc->stream_size = tag_size;
|
||||
|
||||
awc->num_samples = 48000;
|
||||
awc->sample_rate = 48000;
|
||||
awc->codec = 0xFF;
|
||||
awc->channels = 1;
|
||||
break;
|
||||
|
||||
case 0xA3: /* block-to-sample table (32b x number of blocks w/ num_samples at the start of each block)
|
||||
* or frame-size table (16b x number of frames) in some cases (ex. sfx+mpeg but not sfx+vorbis) */
|
||||
case 0xBD: /* events (32bx4): type_hash, params_hash, timestamp_ms, flags */
|
||||
case 0x5C: /* animation/RSC config? */
|
||||
default: /* 0x68=midi?, 0x36=hash thing?, 0x2B=sizes, 0x5A/0xD9=? */
|
||||
case 0x5C: /* animation/RSC info? */
|
||||
case 0x81: /* animation/CSR info? */
|
||||
case 0x36: /* list of hash-things? */
|
||||
case 0x2B: /* events/sizes? */
|
||||
default: /* 0x68=midi?, 0x5A/0xD9=? */
|
||||
//VGM_LOG("AWC: ignoring unknown tag 0x%02x\n", tag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* in music mode there tags for other streams we don't use, except for vorbis. streams have vorbis setup info for channels, but
|
||||
* channel<>stream order doesn't match, so we need to assign setup to channels. All setups seem to be the same though. */
|
||||
if (awc->is_streamed && awc->codec == 0x08) {
|
||||
offset = awc->tags_offset;
|
||||
offset += 0x08 * awc->stream_info[0].tag_count; /* ignore 1st/music stream */
|
||||
|
||||
for (int stream = 1; stream < entries; stream++) {
|
||||
for (int tag = 0; tag < awc->stream_info[stream].tag_count; tag++) {
|
||||
uint64_t tag_header = read_u64(offset,sf);
|
||||
uint8_t tag_type = ((tag_header >> 56) & 0xFF); /* 8b */
|
||||
//uint32_t tag_size = ((tag_header >> 28) & 0x0FFFFFFF); /* 28b */
|
||||
uint32_t tag_offset = ((tag_header >> 0) & 0x0FFFFFFF); /* 28b */
|
||||
|
||||
switch(tag_type) {
|
||||
case 0x7f: /* vorbis setup */
|
||||
/* find which channel uses this stream's data */
|
||||
for (int ch = 0; ch < awc->channels; ch++) {
|
||||
if (awc->channel_hash[ch] == awc->stream_info[stream].hash_id) {
|
||||
awc->vorbis_offset[ch] = tag_offset;
|
||||
//awc->vorbis_size[ch] = tag_size; /* not needed (implicit)*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
offset += 0x08;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!awc->stream_offset) {
|
||||
VGM_LOG("AWC: stream offset not found\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* vorbis offset table, somehow offsets are unordered and can go before tags */
|
||||
if (awc->is_music && awc->codec == 0x08) {
|
||||
offset += 0x08 * tag_count;
|
||||
|
||||
for (ch = 0; ch < awc->channels; ch++) {
|
||||
awc->vorbis_offset[ch] = read_u16(offset + 0x08*ch + 0x00, sf);
|
||||
/* 0x02: always 0xB000? */
|
||||
/* 0x04: always 0x00CD? */
|
||||
/* 0x06: always 0x7F00? */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* In music mode, data is divided into blocks of block_chunk size with padding.
|
||||
* Each block has a header/seek table and interleaved data for all channels */
|
||||
{
|
||||
int32_t seek_start = read_u32(awc->stream_offset, sf); /* -1 in later (RDR2) versions */
|
||||
if (awc->is_music && !(seek_start == 0 || seek_start == -1)) {
|
||||
VGM_LOG("AWC: music found, but block doesn't start with seek table at %x\n", (uint32_t)awc->stream_offset);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
|
@ -414,140 +553,113 @@ fail:
|
|||
|
||||
/* ************************************************************************* */
|
||||
|
||||
//TODO: this method won't work properly, needs internal handling of blocks.
|
||||
//
|
||||
// This setups a decoder per block, but seems Vorbis' uses first frame as setup so it
|
||||
// returns samples (576 vs 1024), making num_samples count in each block being off + causing
|
||||
// gaps. So they must be using a single encoder + setting decode_to_discard per block
|
||||
// to ge the thing working.
|
||||
//
|
||||
// However since blocks are probably also used for seeking, maybe they aren't resetting
|
||||
// the decoder when seeking? or they force first frame to be 1024?
|
||||
//
|
||||
// In case of Vorvis, when setting skip samples seems repeated data from last block is
|
||||
// exactly last 0x800 bytes of that channel.
|
||||
|
||||
static VGMSTREAM* build_block_vgmstream(STREAMFILE* sf, awc_header* awc, int channel, int32_t num_samples, int32_t skip_samples, off_t block_start, size_t block_size) {
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
static VGMSTREAM* build_blocks_vgmstream(STREAMFILE* sf, awc_header* awc, int channel) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
int block_channels = 1;
|
||||
uint32_t substream_size, substream_offset;
|
||||
|
||||
|
||||
/* setup custom IO streamfile that removes AWC's odd blocks (not perfect but serviceable) */
|
||||
temp_sf = setup_awc_streamfile(sf, awc->stream_offset, awc->stream_size, awc->block_chunk, awc->channels, channel, awc->codec, awc->big_endian);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
substream_offset = 0x00;
|
||||
substream_size = get_streamfile_size(temp_sf);
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(block_channels, 0);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = awc->sample_rate;
|
||||
vgmstream->num_samples = num_samples - skip_samples;
|
||||
vgmstream->stream_size = block_size;
|
||||
vgmstream->meta_type = meta_AWC;
|
||||
vgmstream->sample_rate = awc->sample_rate;
|
||||
vgmstream->num_samples = awc->num_samples;
|
||||
vgmstream->stream_size = awc->stream_size;
|
||||
|
||||
vgmstream->stream_size = substream_size;
|
||||
|
||||
|
||||
switch(awc->codec) {
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case 0x05: { /* XMA2 (X360) */
|
||||
vgmstream->codec_data = init_ffmpeg_xma2_raw(temp_sf, substream_offset, substream_size, awc->num_samples, block_channels, awc->sample_rate, 0, 0);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
xma_fix_raw_samples(vgmstream, temp_sf, substream_offset, substream_size, 0, 0,0); /* samples are ok? */
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef VGM_USE_VORBIS
|
||||
case 0x08: { /* Vorbis (PC) [Red Dead Redemption 2 (PC)] */
|
||||
case 0x08: {
|
||||
vorbis_custom_config cfg = {0};
|
||||
|
||||
cfg.channels = 1;
|
||||
cfg.sample_rate = awc->sample_rate;
|
||||
cfg.header_offset = awc->vorbis_offset[channel];
|
||||
//cfg.skip_samples = skip_samples; //todo
|
||||
cfg.header_offset = awc->vorbis_offset[channel]; /* setup page goes separate */
|
||||
|
||||
vgmstream->codec_data = init_vorbis_custom(sf, block_start, VORBIS_AWC, &cfg);
|
||||
/* note that it needs sf on init to read the header + start offset for later, and temp_sf on decode to read data */
|
||||
vgmstream->codec_data = init_vorbis_custom(sf, substream_offset, VORBIS_AWC, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->coding_type = coding_VORBIS_custom;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
case 0x0F: {
|
||||
atrac9_config cfg = {0};
|
||||
|
||||
/* read from first block (all blocks have it but same thing), see awc_streamfile.h */
|
||||
uint32_t extradata_offset = awc->stream_offset + 0x10 * awc->channels + 0x70 * channel + 0x0c;
|
||||
|
||||
cfg.channels = block_channels;
|
||||
cfg.encoder_delay = 0; //?
|
||||
cfg.config_data = read_u32be(extradata_offset, sf);
|
||||
|
||||
vgmstream->codec_data = init_atrac9(&cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_ATRAC9;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, block_start))
|
||||
if (!vgmstream_open_stream(vgmstream, temp_sf, substream_offset))
|
||||
goto fail;
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
fail:
|
||||
;VGM_LOG("AWB: can't open decoder\n");
|
||||
close_vgmstream(vgmstream);
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static VGMSTREAM* build_blocks_vgmstream(STREAMFILE* sf, awc_header* awc, int channel) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
segmented_layout_data* data = NULL;
|
||||
int i, ch;
|
||||
int blocks = awc->stream_size / awc->block_chunk + (awc->stream_size % awc->block_chunk ? 1 : 0) ;
|
||||
|
||||
|
||||
/* init layout */
|
||||
data = init_layout_segmented(blocks);
|
||||
if (!data) goto fail;
|
||||
|
||||
/* one segment per block of this channel */
|
||||
for (i = 0; i < blocks; i++) {
|
||||
off_t block_offset = awc->stream_offset + i * awc->block_chunk;
|
||||
int32_t num_samples = 0, skip_samples = 0;
|
||||
uint32_t header_skip = 0, block_skip = 0, block_start = 0, block_data = 0;
|
||||
|
||||
/* read stupid block crap to get proper offsets and whatnot, format:
|
||||
* - per channel: number of channel entries + skip samples + num samples
|
||||
* - per channel: seek table with N entries */
|
||||
for (ch = 0; ch < awc->channels; ch++) {
|
||||
/* 0x00: -1 */
|
||||
int entries = read_u32le(block_offset + 0x18 * ch + 0x04, sf);
|
||||
int32_t entry_skip = read_u32le(block_offset + 0x18 * ch + 0x08, sf);
|
||||
int32_t entry_samples = read_u32le(block_offset + 0x18 * ch + 0x0c, sf);
|
||||
|
||||
if (ch == channel) {
|
||||
num_samples = entry_samples;
|
||||
skip_samples = entry_skip;
|
||||
|
||||
block_start = block_offset + block_skip;
|
||||
block_data = entries * 0x800;
|
||||
}
|
||||
|
||||
header_skip += 0x18 + entries * 0x04;
|
||||
block_skip += entries * 0x800;
|
||||
}
|
||||
|
||||
if (!block_start)
|
||||
goto fail;
|
||||
|
||||
header_skip = align_size_to_block(header_skip, 0x800);
|
||||
block_start += header_skip;
|
||||
//;VGM_LOG("AWC: build ch%i, block=%i at %lx, o=%x, s=%x, ns=%i, ss=%i\n", channel, i, block_offset, block_start, block_data, num_samples, skip_samples);
|
||||
|
||||
data->segments[i] = build_block_vgmstream(sf, awc, channel, num_samples, skip_samples, block_start, block_data);
|
||||
if (!data->segments[i]) goto fail;
|
||||
}
|
||||
|
||||
/* setup VGMSTREAMs */
|
||||
if (!setup_layout_segmented(data))
|
||||
goto fail;
|
||||
|
||||
/* build the layout VGMSTREAM */
|
||||
vgmstream = allocate_segmented_vgmstream(data, 0, 0, 0);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
if (!vgmstream)
|
||||
free_layout_segmented(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
|
||||
/* Make layers per channel for AWC's abhorrent blocks.
|
||||
/* Make layers per channel for AWC's abhorrent blocks (see read_awb_block).
|
||||
*
|
||||
* File has N channels = N streams, that use their own mono decoder.
|
||||
* Each block then has header + seek table for all channels. But in each block there is
|
||||
* a "skip samples" value per channel, and blocks repeat some data from last block
|
||||
* for this, so PCM must be discarded. Also, channels in a block don't need to have
|
||||
* the same number of samples.
|
||||
* A "music" .awc has N channels = N streams (each using their own mono decoder) chunked in "blocks".
|
||||
* Each block then has header + seek table + etc for all channels. But when blocks change, each channel
|
||||
* may have a "skip samples" value and blocks repeat some data from last block, so output PCM must be
|
||||
* discarded to avoid channels desyncing. Channels in a block don't need to have the same number of samples.
|
||||
* (mainly seen in MPEG).
|
||||
*/
|
||||
//TODO: this method won't fully work, needs feed decoder + block handler that interacts with decoder(s?)
|
||||
// (doesn't use multiple decoders since default encoder delay in Vorbis would discard too much per block)
|
||||
//
|
||||
// When blocks change presumably block handler needs to tell decoder to finish decoding all from prev block
|
||||
// then skip samples from next decodes. Also since samples may vary per channel, each would handle blocks
|
||||
// independently.
|
||||
//
|
||||
// This can be simulated by making one decoder per block (segmented, but opens too many SFs and can't skip
|
||||
// samples correctly), or with a custom STREAMFILE that skips repeated block (works ok-ish but not all codecs).
|
||||
static layered_layout_data* build_layered_awc(STREAMFILE* sf, awc_header* awc) {
|
||||
int i;
|
||||
layered_layout_data* data = NULL;
|
||||
|
@ -571,4 +683,3 @@ fail:
|
|||
free_layout_layered(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
#ifndef _AWC_DECRYPTION_STREAMFILE_H_
|
||||
#define _AWC_DECRYPTION_STREAMFILE_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "../streamfile.h"
|
||||
#include "../util/cipher_xxtea.h"
|
||||
#include "../util/companion_files.h"
|
||||
#include "../util.h"
|
||||
|
||||
#define MAX_BLOCK_SIZE 0x6e4000 /* usually 0x80000, observed max for Nch files ~= 8MB */
|
||||
|
||||
/* decrypts xxtea blocks */
|
||||
typedef struct {
|
||||
uint32_t data_offset; /* where encryption data starts */
|
||||
uint32_t data_size; /* encrypted size */
|
||||
uint32_t block_size; /* xxtea block chunk size (big) */
|
||||
uint32_t key[4]; /* decryption key */
|
||||
uint8_t* buf; /* decrypted block */
|
||||
uint32_t read_offset; /* last read offset (aligned to data_offset + block_size) */
|
||||
} awcd_io_data;
|
||||
|
||||
|
||||
static int awcd_io_init(STREAMFILE* sf, awcd_io_data* data) {
|
||||
/* ktsr keys start with size then random bytes (usually 7), assumed max 0x20 */
|
||||
|
||||
data->buf = malloc(data->block_size);
|
||||
if (!data->buf)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void awcd_io_close(STREAMFILE* sf, awcd_io_data* data) {
|
||||
free(data->buf);
|
||||
}
|
||||
|
||||
/* reads from current block; offset/length must be within data_offset + data_size (handled externally) */
|
||||
static int read_block(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, awcd_io_data* data) {
|
||||
|
||||
/* detect if we requested offset falls within current decrypted block, otherwise read + decrypt */
|
||||
off_t block_offset = (offset - data->data_offset) / data->block_size * data->block_size + data->data_offset; /* closest block */
|
||||
int block_read = clamp_u32(data->block_size, 0, data->data_size - (block_offset - data->data_offset)); /* last block can be smaller */
|
||||
if (data->read_offset != block_offset) {
|
||||
int bytes = read_streamfile(data->buf, block_offset, block_read, sf);
|
||||
if (bytes != block_read)
|
||||
return 0;
|
||||
xxtea_decrypt(data->buf, block_read, data->key);
|
||||
data->read_offset = block_offset;
|
||||
}
|
||||
|
||||
int buf_pos = offset - block_offset; /* within current block */
|
||||
int to_do = clamp_u32(length, 0, block_read - buf_pos);
|
||||
memcpy(dest, data->buf + buf_pos, to_do);
|
||||
|
||||
return to_do;
|
||||
}
|
||||
|
||||
/* xxtea works with big chunks, so depending on requested offset read into buf + decrypt + copy */
|
||||
static size_t awcd_io_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, awcd_io_data* data) {
|
||||
size_t total_bytes = 0;
|
||||
|
||||
while (length > 0) {
|
||||
int bytes;
|
||||
|
||||
if (offset < data->data_offset) {
|
||||
int to_do = clamp_u32(length, 0, data->data_offset - offset);
|
||||
bytes = read_streamfile(dest, offset, to_do, sf);
|
||||
}
|
||||
else if (offset >= data->data_offset + data->data_size) {
|
||||
int to_do = length;
|
||||
bytes = read_streamfile(dest, offset, to_do, sf);
|
||||
}
|
||||
else {
|
||||
bytes = read_block(sf, dest, offset, length, data);
|
||||
}
|
||||
|
||||
dest += bytes;
|
||||
offset += bytes;
|
||||
length -= bytes;
|
||||
total_bytes += bytes;
|
||||
|
||||
/* may be smaller than expected when reading between blocks but shouldn't be 0 */
|
||||
if (bytes == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return total_bytes;
|
||||
}
|
||||
|
||||
|
||||
/* decrypts AWC blocks (seen in GTA5 PC) using .awckey + xxtea algorithm (only for target subsong).
|
||||
*
|
||||
* Reversed from OpenIV.exe 4.1/2023 (see fun_007D5EA8) b/c it was easier than from GTA5.exe itself.
|
||||
* OpenIV includes 2 keys, one for PC and other for probably PS4 (since other platforms aren't encrypted);
|
||||
* neither seem to be found in GTA5.exe though (packed/derived?). Unlike standard xxtea OpenIV only has decryption.
|
||||
* Keys must be provided externally, could autodetect but given T2 suing habits let's err on the side of caution. */
|
||||
static STREAMFILE* setup_awcd_streamfile(STREAMFILE* sf, uint32_t data_offset, uint32_t data_size, uint32_t block_size) {
|
||||
STREAMFILE* new_sf = NULL;
|
||||
awcd_io_data io_data = {0};
|
||||
|
||||
uint8_t key[0x10];
|
||||
size_t key_size = read_key_file(key, sizeof(key), sf);
|
||||
if (key_size != sizeof(key))
|
||||
goto fail;
|
||||
|
||||
for (int i = 0; i < sizeof(key) / 4; i++) {
|
||||
io_data.key[i] = get_u32be(key + i * 0x04);
|
||||
}
|
||||
|
||||
if (data_offset == 0 || data_size == 0)
|
||||
goto fail;
|
||||
if (block_size == 0) /* for non-blocked audio (small streams) */
|
||||
block_size = data_size;
|
||||
if (block_size > MAX_BLOCK_SIZE || (block_size % 0x04) != 0)
|
||||
goto fail;
|
||||
|
||||
io_data.data_offset = data_offset;
|
||||
io_data.data_size = data_size;
|
||||
io_data.block_size = block_size;
|
||||
|
||||
/* setup subfile */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_streamfile_ex_f(new_sf, &io_data, sizeof(awcd_io_data), awcd_io_read, NULL, awcd_io_init, awcd_io_close);
|
||||
return new_sf;
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
253
Frameworks/vgmstream/vgmstream/src/meta/awc_streamfile.h
Normal file
253
Frameworks/vgmstream/vgmstream/src/meta/awc_streamfile.h
Normal file
|
@ -0,0 +1,253 @@
|
|||
#ifndef _AWC_STREAMFILE_H_
|
||||
#define _AWC_STREAMFILE_H_
|
||||
#include "deblock_streamfile.h"
|
||||
#include "../util/endianness.h"
|
||||
|
||||
#define AWC_MAX_MUSIC_CHANNELS 32 /* seen ~24 */
|
||||
|
||||
/* ************************************************************************* */
|
||||
|
||||
typedef struct {
|
||||
int start_entry; /* innacurate! */
|
||||
int entries;
|
||||
int32_t channel_skip;
|
||||
int32_t channel_samples;
|
||||
|
||||
uint32_t frame_size;
|
||||
|
||||
/* derived */
|
||||
uint32_t chunk_start; /* relative to block offset */
|
||||
uint32_t chunk_size; /* size of this channel's data (not including padding) */
|
||||
} awc_block_t;
|
||||
|
||||
typedef struct {
|
||||
int big_endian;
|
||||
uint8_t codec;
|
||||
int channels;
|
||||
uint32_t block_offset;
|
||||
awc_block_t blk[AWC_MAX_MUSIC_CHANNELS];
|
||||
} awc_block_info_t;
|
||||
|
||||
/* Block format:
|
||||
* - block header for all channels (needed to find frame start)
|
||||
* - frames from channel 1
|
||||
* - ...
|
||||
* - frames from channel N
|
||||
* - usually there is padding between channels or blocks (usually 0s but seen 0x97 in AT9)
|
||||
*
|
||||
* Header format:
|
||||
* - per channel (frame start table)
|
||||
* 0x00: start entry for that channel? (-1 in vorbis)
|
||||
* may be off by +1/+2?
|
||||
* ex. on block 0, ch0/1 have 0x007F frames, a start entry is: ch0=0x0000, ch1=0x007F (MP3)
|
||||
* ex. on block 0, ch0/1 have 0x02A9 frames, a start entry is: ch0=0x0000, ch1=0x02AA (AT9) !!
|
||||
* (sum of all values from all channels may go beyond all posible frames, no idea)
|
||||
* 0x04: frames in this channel (may be different between channels)
|
||||
* 'frames' here may be actual single decoder frames or a chunk of frames
|
||||
* 0x08: samples to discard in the beginning of this block (MPEG/XMA2/Vorbis only?)
|
||||
* 0x0c: samples in channel (for MPEG/XMA2 can vary between channels)
|
||||
* full samples without removing samples to discard
|
||||
* (next fields only exists for MPEG, Vorbis or some IMA versions)
|
||||
* 0x10: (MPEG only, empty otherwise) close to number of frames but varies a bit?
|
||||
* 0x14: (MPEG only, empty otherwise) channel chunk size (not counting padding)
|
||||
* - for each channel (seek table)
|
||||
* 32b * entries = global samples per frame in each block (for MPEG probably per full frame)
|
||||
* (AT9 doesn't have a seek table as it's CBR)
|
||||
* - per channel (ATRAC9/DSP extra info):
|
||||
* 0x00: "D11A"
|
||||
* 0x04: frame size
|
||||
* 0x06: frame samples
|
||||
* 0x08: flags? (0x0103=AT9, 0x0104=DSP)
|
||||
* 0x0a: sample rate
|
||||
* 0x0c: ATRAC9 config (repeated but same for all blocks) or "D11E" (DSP)
|
||||
* 0x10-0x70: padding with 0x77 (ATRAC3) or standard DSP header for original full file (DSP)
|
||||
* - padding until channel data start, depending on codec (DSP/ATRAC9: one, others: aligned to 0x800)
|
||||
* - per channel:
|
||||
* 0xNN: channel frames
|
||||
* 0xNN: may have padding between channels depending on codec (mainly MPEG/XMA)
|
||||
* - padding until this block's end
|
||||
*/
|
||||
static bool read_awb_block(STREAMFILE* sf, awc_block_info_t* bi) {
|
||||
read_s32_t read_s32 = bi->big_endian ? read_s32be : read_s32le;
|
||||
read_u16_t read_u16 = bi->big_endian ? read_u16be : read_u16le;
|
||||
|
||||
uint32_t channel_entry_size, seek_entry_size, extra_entry_size, header_padding;
|
||||
uint32_t offset = bi->block_offset;
|
||||
int channels = bi->channels;
|
||||
/* read stupid block crap + derived info at once so hopefully it's a bit easier to understand */
|
||||
|
||||
switch(bi->codec) {
|
||||
case 0x05: /* XMA2 */
|
||||
channel_entry_size = 0x10;
|
||||
seek_entry_size = 0x04;
|
||||
extra_entry_size = 0x00;
|
||||
header_padding = 0x800;
|
||||
break;
|
||||
case 0x08: /* Vorbis */
|
||||
channel_entry_size = 0x18;
|
||||
seek_entry_size = 0x04;
|
||||
extra_entry_size = 0x00;
|
||||
header_padding = 0x800;
|
||||
break;
|
||||
case 0x0F: /* ATRAC9 */
|
||||
channel_entry_size = 0x10;
|
||||
seek_entry_size = 0x00;
|
||||
extra_entry_size = 0x70;
|
||||
header_padding = 0x00;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* channel info table */
|
||||
for (int ch = 0; ch < bi->channels; ch++) {
|
||||
bi->blk[ch].start_entry = read_s32(offset + 0x00, sf);
|
||||
bi->blk[ch].entries = read_s32(offset + 0x04, sf);
|
||||
bi->blk[ch].channel_skip = read_s32(offset + 0x08, sf);
|
||||
bi->blk[ch].channel_samples = read_s32(offset + 0x0c, sf);
|
||||
/* others: optional */
|
||||
|
||||
offset += channel_entry_size;
|
||||
}
|
||||
|
||||
/* seek table */
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
offset += bi->blk[ch].entries * seek_entry_size;
|
||||
}
|
||||
|
||||
/* extra table and derived info */
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
switch(bi->codec) {
|
||||
case 0x05: /* XMA2 */
|
||||
case 0x08: /* Vorbis */
|
||||
/* each 'frame'/entry in Vorbis is actually N vorbis frames then padding up to 0x800
|
||||
* (more or less like a big Ogg page or XMA 'frame'). Padding is considered part of
|
||||
* the data and handled by the decoder, since sfx (non-blocked) algo have it. */
|
||||
bi->blk[ch].frame_size = 0x800;
|
||||
bi->blk[ch].chunk_size = bi->blk[ch].entries * bi->blk[ch].frame_size;
|
||||
break;
|
||||
|
||||
case 0x0F: /* ATRAC9 */
|
||||
bi->blk[ch].frame_size = read_u16(offset + 0x04, sf);
|
||||
bi->blk[ch].chunk_size = bi->blk[ch].entries * bi->blk[ch].frame_size;
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
offset += extra_entry_size;
|
||||
}
|
||||
|
||||
/* header done, move into data start */
|
||||
if (header_padding) {
|
||||
/* padding on the current size rather than file offset (block meant to be read into memory, probably) */
|
||||
uint32_t header_size = offset - bi->block_offset;
|
||||
offset = bi->block_offset + align_size_to_block(header_size, header_padding);
|
||||
}
|
||||
|
||||
/* set frame starts per channel */
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
bi->blk[ch].chunk_start = offset - bi->block_offset;
|
||||
offset += bi->blk[ch].chunk_size;
|
||||
}
|
||||
|
||||
/* beyond this is padding until chunk_start */
|
||||
|
||||
return true;
|
||||
fail:
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Find data that repeats in the beginning of a new block at the end of last block.
|
||||
* When a new block starts there is some repeated data + channel_skip (for seeking + encoder delay?).
|
||||
* Detect it so decoder may ignore it. */
|
||||
static uint32_t get_block_repeated_size(STREAMFILE* sf, awc_block_info_t* bi, int channel) {
|
||||
|
||||
if (bi->blk[channel].channel_skip == 0)
|
||||
return 0;
|
||||
|
||||
switch(bi->codec) {
|
||||
case 0x05: { /* XMA2 */
|
||||
const uint32_t samples_per_subframe = 512;
|
||||
uint32_t samples_this_frame;
|
||||
uint8_t subframes;
|
||||
uint32_t offset = bi->block_offset + bi->blk[channel].chunk_start;
|
||||
int repeat_samples = bi->blk[channel].channel_skip;
|
||||
|
||||
//TODO: fix (needs proper decoder + sample discard)
|
||||
/* Repeat samples are the number of decoded samples to discard, but in this streamfile we can't do that.
|
||||
* Also XMA is VBR, and may encode silent frames with up to 63 subframes yet we may have few repeat samples.
|
||||
* We could find out how many subframes of 512 samples to skip, then adjust the XMA frame header, though
|
||||
* output will be slightly off since subframes are related.
|
||||
*
|
||||
* For now just skip a full frame depending on the number of subframes vs repeat samples.
|
||||
* Most files work ok-ish but channels may desync slightly. */
|
||||
|
||||
subframes = (read_u8(offset,sf) >> 2) & 0x3F; /* peek into frame header */
|
||||
samples_this_frame = subframes * samples_per_subframe;
|
||||
if (repeat_samples >= (int)(samples_this_frame * 0.13)) { /* skip mosts */
|
||||
return bi->blk[channel].frame_size;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
case 0x08: /* Vorbis */
|
||||
/* when data repeats seems to clone exactly the last super-frame */
|
||||
return bi->blk[channel].frame_size;
|
||||
|
||||
case 0x0F: /* ATRAC9 */
|
||||
default:
|
||||
VGM_LOG("AWC: found channel skip in codec %x\n", bi->codec); /* not seen */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
|
||||
static void block_callback(STREAMFILE *sf, deblock_io_data* data) {
|
||||
int channel = data->cfg.track_number;
|
||||
awc_block_info_t bi = {0};
|
||||
|
||||
bi.big_endian = data->cfg.big_endian;
|
||||
bi.block_offset = data->physical_offset;
|
||||
bi.channels = data->cfg.track_count;
|
||||
bi.codec = data->cfg.track_type;
|
||||
|
||||
if (!read_awb_block(sf, &bi))
|
||||
return; //???
|
||||
|
||||
uint32_t repeat_size = get_block_repeated_size(sf, &bi, channel);
|
||||
|
||||
data->block_size = data->cfg.chunk_size;
|
||||
data->skip_size = bi.blk[channel].chunk_start + repeat_size;
|
||||
data->data_size = bi.blk[channel].chunk_size - repeat_size;
|
||||
}
|
||||
|
||||
/* deblocks AWC blocks */
|
||||
static STREAMFILE* setup_awc_streamfile(STREAMFILE* sf, uint32_t stream_offset, uint32_t stream_size, uint32_t block_size, int channels, int channel, uint8_t codec, int big_endian) {
|
||||
STREAMFILE* new_sf = NULL;
|
||||
deblock_config_t cfg = {0};
|
||||
|
||||
if (channels >= AWC_MAX_MUSIC_CHANNELS)
|
||||
return NULL;
|
||||
|
||||
cfg.track_number = channel;
|
||||
cfg.track_count = channels;
|
||||
cfg.stream_start = stream_offset;
|
||||
cfg.stream_size = stream_size;
|
||||
cfg.chunk_size = block_size;
|
||||
cfg.track_type = codec;
|
||||
cfg.big_endian = big_endian;
|
||||
//cfg.physical_offset = stream_offset;
|
||||
//cfg.logical_size = awc_xma_io_size(sf, &cfg); /* force init */
|
||||
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);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,114 +0,0 @@
|
|||
#ifndef _AWC_XMA_STREAMFILE_H_
|
||||
#define _AWC_XMA_STREAMFILE_H_
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
|
||||
static size_t get_block_header_size(STREAMFILE* sf, off_t offset, int channels);
|
||||
static size_t get_repeated_data_size(STREAMFILE* sf, off_t next_offset, size_t repeat_samples);
|
||||
static size_t get_block_skip_count(STREAMFILE* sf, off_t offset, int channel);
|
||||
|
||||
static void block_callback(STREAMFILE *sf, deblock_io_data* data) {
|
||||
const size_t frame_size = 0x800;
|
||||
int channel = data->cfg.track_number;
|
||||
int channels = data->cfg.track_count;
|
||||
|
||||
/* Blocks have a header then data per channel, each with a different num_samples/frames,
|
||||
* separate (first all frames of ch0, then ch1, etc), padded, and sometimes the last few
|
||||
* frames of a channel are repeated in the new block (marked with "repeat samples"). */
|
||||
size_t header_size = get_block_header_size(sf, data->physical_offset, channels);
|
||||
/* header table entries = frames... I hope */
|
||||
size_t others_size = get_block_skip_count(sf, data->physical_offset, channel) * frame_size;
|
||||
//size_t skip_size = read_u32be(data->physical_offset + 0x10*channel + 0x00, sf) * frame_size;
|
||||
size_t data_size = read_u32be(data->physical_offset + 0x10*channel + 0x04, sf) * frame_size;
|
||||
size_t repeat_samples = read_u32be(data->physical_offset + 0x10*channel + 0x08, sf);
|
||||
size_t repeat_size = 0;
|
||||
|
||||
data->block_size = data->cfg.chunk_size;
|
||||
|
||||
/* if there are repeat samples current block repeats some frames from last block, find out size */
|
||||
if (repeat_samples) {
|
||||
off_t data_offset = data->physical_offset + header_size + others_size;
|
||||
repeat_size = get_repeated_data_size(sf, data_offset, repeat_samples);
|
||||
}
|
||||
|
||||
data->skip_size = header_size + others_size + repeat_size;
|
||||
data->data_size = data_size - repeat_size;
|
||||
}
|
||||
|
||||
/* block header size, aligned/padded to 0x800 */
|
||||
static size_t get_block_header_size(STREAMFILE* sf, off_t offset, int channels) {
|
||||
size_t header_size = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < channels; i++) {
|
||||
header_size += 0x10;
|
||||
header_size += read_u32be(offset + 0x10*i + 0x04, sf) * 0x04; /* entries in the table */
|
||||
}
|
||||
|
||||
if (header_size % 0x800) /* padded */
|
||||
header_size += 0x800 - (header_size % 0x800);
|
||||
|
||||
return header_size;
|
||||
}
|
||||
|
||||
/* find data that repeats in the beginning of a new block at the end of last block */
|
||||
static size_t get_repeated_data_size(STREAMFILE* sf, off_t next_offset, size_t repeat_samples) {
|
||||
const size_t frame_size = 0x800;
|
||||
const size_t samples_per_subframe = 512;
|
||||
size_t samples_this_frame;
|
||||
uint8_t subframes;
|
||||
|
||||
//todo: fix this
|
||||
/* Repeat samples are the number of decoded samples to discard, but in this streamfile we can't do that.
|
||||
* Also XMA is VBR, and may encode silent frames with up to 63 subframes yet we may have few repeat samples.
|
||||
* We could find out how many subframes of 512 samples to skip, then adjust the XMA frame header, though
|
||||
* output will be slightly off since subframes are related.
|
||||
*
|
||||
* For now just skip a full frame depending on the number of subframes vs repeat samples.
|
||||
* Most files work ok-ish but channels may desync slightly. */
|
||||
|
||||
subframes = ((uint8_t)read_8bit(next_offset,sf) >> 2) & 0x3F; /* peek into frame header */
|
||||
samples_this_frame = subframes*samples_per_subframe;
|
||||
if (repeat_samples >= (int)(samples_this_frame*0.13)) { /* skip mosts */
|
||||
return frame_size;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* header has a skip value, but somehow it's sometimes bigger than expected (WHY!?!?) so just sum all */
|
||||
static size_t get_block_skip_count(STREAMFILE* sf, off_t offset, int channel) {
|
||||
size_t skip_count = 0;
|
||||
int i;
|
||||
|
||||
//skip_size = read_u32be(offset + 0x10*channel + 0x00, sf); /* wrong! */
|
||||
for (i = 0; i < channel; i++) {
|
||||
skip_count += read_u32be(offset + 0x10*i + 0x04, sf); /* number of frames of this channel */
|
||||
}
|
||||
|
||||
return skip_count;
|
||||
}
|
||||
|
||||
|
||||
/* Deblocks interleaved XMA in AWC blocks */
|
||||
static STREAMFILE* setup_awc_xma_streamfile(STREAMFILE *sf, off_t stream_offset, size_t stream_size, size_t block_size, int channel_count, int channel) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
deblock_config_t cfg = {0};
|
||||
|
||||
cfg.track_number = channel;
|
||||
cfg.track_count = channel_count;
|
||||
cfg.stream_start = stream_offset;
|
||||
cfg.stream_size = stream_size;
|
||||
cfg.chunk_size = block_size;
|
||||
//cfg.physical_offset = stream_offset;
|
||||
//cfg.logical_size = awc_xma_io_size(sf, &cfg); /* force init */
|
||||
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);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _AWC_XMA_STREAMFILE_H_ */
|
|
@ -6,7 +6,7 @@
|
|||
/* AWD - Audio Wave Dictionary (RenderWare) */
|
||||
VGMSTREAM* init_vgmstream_awd(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
char header_name[STREAM_NAME_SIZE], stream_name[STREAM_NAME_SIZE];
|
||||
char file_name[STREAM_NAME_SIZE], header_name[STREAM_NAME_SIZE], stream_name[STREAM_NAME_SIZE];
|
||||
int /*bit_depth = 0,*/ channels = 0, sample_rate = 0, stream_codec = -1, total_subsongs = 0, target_subsong = sf->stream_index;
|
||||
int interleave, loop_flag;
|
||||
off_t data_offset, header_name_offset, misc_data_offset, linked_list_offset, wavedict_offset;
|
||||
|
@ -115,7 +115,8 @@ VGMSTREAM* init_vgmstream_awd(STREAMFILE* sf) {
|
|||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
|
||||
if (header_name_offset)
|
||||
get_streamfile_basename(sf, file_name, STREAM_NAME_SIZE);
|
||||
if (header_name_offset && strcmp(file_name, header_name))
|
||||
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%s/%s", header_name, stream_name);
|
||||
else
|
||||
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%s", stream_name);
|
||||
|
|
|
@ -1,66 +1,58 @@
|
|||
#ifndef _BGW_STREAMFILE_H_
|
||||
#define _BGW_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
#define BGW_KEY_MAX (0xC0*2)
|
||||
|
||||
typedef struct {
|
||||
uint8_t key[BGW_KEY_MAX];
|
||||
size_t key_size;
|
||||
} bgw_decryption_data;
|
||||
|
||||
/* Encrypted ATRAC3 info from Moogle Toolbox (https://sourceforge.net/projects/mogbox/) */
|
||||
static size_t bgw_decryption_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, bgw_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++) {
|
||||
dest[i] ^= data->key[(offset + i) % data->key_size];
|
||||
}
|
||||
|
||||
//todo: a few files (music069.bgw, music071.bgw, music900.bgw) have the last frames unencrypted,
|
||||
// though they are blank and encoder ignores wrongly decrypted frames and outputs blank samples as well
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
static STREAMFILE* setup_bgw_atrac3_streamfile(STREAMFILE *streamFile, off_t subfile_offset, size_t subfile_size, size_t frame_size, int channels) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
bgw_decryption_data io_data = {0};
|
||||
size_t io_data_size = sizeof(bgw_decryption_data);
|
||||
int ch;
|
||||
|
||||
/* setup decryption with key (first frame + modified channel header) */
|
||||
if (frame_size*channels == 0 || frame_size*channels > BGW_KEY_MAX) goto fail;
|
||||
|
||||
io_data.key_size = read_streamfile(io_data.key, subfile_offset, frame_size*channels, streamFile);
|
||||
for (ch = 0; ch < channels; ch++) {
|
||||
uint32_t xor = get_32bitBE(io_data.key + frame_size*ch);
|
||||
put_32bitBE(io_data.key + frame_size*ch, xor ^ 0xA0024E9F);
|
||||
}
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_clamp_streamfile(temp_streamFile, subfile_offset,subfile_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, bgw_decryption_read,NULL);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _BGW_STREAMFILE_H_ */
|
||||
#ifndef _BGW_STREAMFILE_H_
|
||||
#define _BGW_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
#define BGW_KEY_MAX (0xC0 * 2)
|
||||
|
||||
typedef struct {
|
||||
uint8_t key[BGW_KEY_MAX];
|
||||
size_t key_size;
|
||||
} bgw_decryption_data;
|
||||
|
||||
/* Encrypted ATRAC3 info from Moogle Toolbox (https://sourceforge.net/projects/mogbox/) */
|
||||
static size_t bgw_decryption_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, bgw_decryption_data* data) {
|
||||
size_t bytes_read = sf->read(sf, dest, offset, length);
|
||||
|
||||
/* decrypt data (xor) */
|
||||
for (int i = 0; i < bytes_read; i++) {
|
||||
dest[i] ^= data->key[(offset + i) % data->key_size];
|
||||
}
|
||||
|
||||
//TODO: a few files (music069.bgw, music071.bgw, music900.bgw) have the last frames unencrypted,
|
||||
// though they are blank and encoder ignores wrongly decrypted frames and outputs blank samples as well.
|
||||
// Only in files that don't loop?
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
static STREAMFILE* setup_bgw_atrac3_streamfile(STREAMFILE* sf, off_t subfile_offset, size_t subfile_size, size_t frame_size, int channels) {
|
||||
STREAMFILE* new_sf = NULL;
|
||||
bgw_decryption_data io_data = {0};
|
||||
size_t io_data_size = sizeof(bgw_decryption_data);
|
||||
|
||||
/* setup decryption with key (first frame + modified channel header) */
|
||||
size_t key_size = frame_size * channels;
|
||||
if (key_size <= 0 || key_size > BGW_KEY_MAX)
|
||||
goto fail;
|
||||
|
||||
io_data.key_size = read_streamfile(io_data.key, subfile_offset, key_size, sf);
|
||||
if (io_data.key_size != key_size)
|
||||
goto fail;
|
||||
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
uint32_t xor = get_u32be(io_data.key + frame_size * ch);
|
||||
put_u32be(io_data.key + frame_size * ch, xor ^ 0xA0024E9F);
|
||||
}
|
||||
|
||||
/* setup subfile */
|
||||
new_sf = open_wrap_streamfile_f(sf);
|
||||
new_sf = open_clamp_streamfile_f(new_sf, subfile_offset,subfile_size);
|
||||
new_sf = open_io_streamfile_f(new_sf, &io_data,io_data_size, bgw_decryption_read,NULL);
|
||||
return new_sf;
|
||||
fail:
|
||||
close_streamfile(new_sf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#include "../coding/coding.h"
|
||||
#include "../util.h"
|
||||
|
||||
static int bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subsongs, size_t* p_stream_size, int* p_channels, int* p_sample_rate, int* p_num_samples);
|
||||
static bool bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subsongs, size_t* p_stream_size, int* p_channels, int* p_sample_rate, int* p_num_samples);
|
||||
|
||||
/* BINK 1/2 - RAD Game Tools movies (audio/video format) */
|
||||
VGMSTREAM* init_vgmstream_bik(STREAMFILE* sf) {
|
||||
|
@ -17,13 +17,14 @@ VGMSTREAM* init_vgmstream_bik(STREAMFILE* sf) {
|
|||
(read_u32be(0x00,sf) & 0xffffff00) != get_id32be("KB2\0"))
|
||||
goto fail;
|
||||
|
||||
/* .bik/bik2/bk2: standard
|
||||
/* .bik/bk2: standard
|
||||
* .bik2: older?
|
||||
* .xmv: Reflections games [Driver: Parallel Lines (Wii), Emergency Heroes (Wii)]
|
||||
* .bik.ps3: Neversoft games [Guitar Hero: Warriors of Rock (PS3)]
|
||||
* .bik.xen: Neversoft games [various Guitar Hero (PC/PS3/X360)]
|
||||
* .vid: Etrange Libellules games [Alice in Wonderland (PC)]
|
||||
* .bika: fake extension for demuxed audio */
|
||||
if (!check_extensions(sf,"bik,bik2,bk2,ps3,xmv,xen,vid,bika"))
|
||||
if (!check_extensions(sf,"bik,bk2,bik2,ps3,xmv,xen,vid,bika"))
|
||||
goto fail;
|
||||
|
||||
/* find target stream info and samples */
|
||||
|
@ -59,79 +60,126 @@ fail:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* official values */
|
||||
#define BINK_MAX_FRAMES 1000000
|
||||
#define BINK_MAX_TRACKS 256
|
||||
|
||||
/**
|
||||
* Gets stream info, and number of samples in a BINK file by reading all frames' headers (as it's VBR),
|
||||
* as they are not in the main header. The header for BINK1 and 2 is the same.
|
||||
* (a ~3 min movie needs ~6000-7000 frames = fseeks, should be fast enough)
|
||||
*/
|
||||
static int bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subsongs, size_t* p_stream_size, int* p_channels, int* p_sample_rate, int* p_num_samples) {
|
||||
uint32_t* offsets = NULL;
|
||||
uint32_t num_frames, num_samples_b = 0;
|
||||
* see: https://wiki.multimedia.cx/index.php?title=Bink_Container */
|
||||
static bool bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subsongs, size_t* p_stream_size, int* p_channels, int* p_sample_rate, int* p_num_samples) {
|
||||
uint32_t* offsets = NULL; //TODO rename
|
||||
uint32_t num_samples_b = 0;
|
||||
off_t cur_offset;
|
||||
int i, j, sample_rate, channels;
|
||||
int total_subsongs;
|
||||
size_t stream_size = 0;
|
||||
|
||||
size_t filesize = get_streamfile_size(sf);
|
||||
uint32_t signature = (read_32bitBE(0x00,sf) & 0xffffff00);
|
||||
uint8_t revision = (read_32bitBE(0x00,sf) & 0xFF);
|
||||
/* known revisions:
|
||||
* bik1: b,d,f,g,h,i,k [no "j"]
|
||||
* bik2: a,d,f,g,h,i,j,k,m,n [no "l"]
|
||||
* (current public binkplay.exe allows 1=f~k and 2=f~n) */
|
||||
uint32_t head_id = read_u32be(0x00,sf);
|
||||
uint32_t file_size = read_u32le(0x04,sf) + 0x08;
|
||||
uint32_t num_frames = read_u32le(0x08,sf);
|
||||
/* 0x0c: largest frame */
|
||||
/* 0x10: num_frames again (found even for files without audio) */
|
||||
/* 0x14: video width (max 32767, apparently can be negative but no different meaning) */
|
||||
/* 0x18: video height (max 32767) */
|
||||
/* 0x1c: fps dividend (must be set) */
|
||||
/* 0x20: fps divider (must be set)
|
||||
* - ex. 2997/100 = 29.97 fps, 30/1 = 30 fps (common values) */
|
||||
//uint32_t video_flags = read_u32le(0x24,sf); /* (scale, alpha, color modes, etc) */
|
||||
/* 0x28: audio tracks */
|
||||
/* video flags:
|
||||
- F0000000 (bits 28-31): width and height scaling bits (doubled, interlaced, etc)
|
||||
- 00100000 (bit 20): has alpha plane
|
||||
- 00040000 (bit 18): unknown, related to packet format? (seen in some bik2 n+, with and w/o audio)
|
||||
- 00020000 (bit 17): grayscale
|
||||
- 00010000 (bit 16): 12 16b numbers? (seen in binkplay)
|
||||
- 00000010 (bit 4): 1 big number? (seems always set in bik1 k+ bik2 i+, but also set in bik2 g wihtout the number)
|
||||
- 00000004 (bit 2): 6 16b (seen in some bik2 n+)
|
||||
- 00000002 (bit 1): unknown (seen in some bik2 n+, with and w/o audio)
|
||||
(from binkplay, flags 0x04 and 0x10000 can't coexist)
|
||||
*/
|
||||
|
||||
if (file_size != get_streamfile_size(sf))
|
||||
return false;
|
||||
if (num_frames <= 0 || num_frames > BINK_MAX_FRAMES)
|
||||
return false; /* (avoids big allocs below) */
|
||||
|
||||
if (read_32bitLE(0x04,sf) + 0x08 != filesize)
|
||||
goto fail;
|
||||
|
||||
num_frames = (uint32_t)read_32bitLE(0x08,sf);
|
||||
if (num_frames == 0 || num_frames > 0x100000) goto fail; /* something must be off (avoids big allocs below) */
|
||||
uint32_t signature = head_id & 0xffffff00;
|
||||
uint8_t revision = head_id & 0xFF;
|
||||
int sample_rate, channels;
|
||||
uint16_t audio_flags;
|
||||
|
||||
/* multichannel/multilanguage audio is usually N streams of stereo/mono, no way to know channel layout */
|
||||
total_subsongs = read_32bitLE(0x28,sf);
|
||||
int total_subsongs = read_s32le(0x28,sf);
|
||||
if (total_subsongs < 1) {
|
||||
vgm_logi("BIK: no audio in movie (ignore)\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs > 255) goto fail;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs > BINK_MAX_TRACKS) goto fail;
|
||||
|
||||
|
||||
/* find stream info and position in offset table */
|
||||
cur_offset = 0x2c;
|
||||
if ((signature == 0x42494B00 && (revision == 0x6b)) || /* k */
|
||||
(signature == 0x4B423200 && (revision == 0x69 || revision == 0x6a || revision == 0x6b))) /* i,j,k */
|
||||
cur_offset += 0x04; /* unknown v2 header field */
|
||||
cur_offset += 0x04*total_subsongs; /* skip streams max packet bytes */
|
||||
sample_rate = (uint16_t)read_16bitLE(cur_offset+0x04*(target_subsong-1)+0x00,sf);
|
||||
channels = (uint16_t)read_16bitLE(cur_offset+0x04*(target_subsong-1)+0x02,sf) & 0x2000 ? 2 : 1; /* stereo flag */
|
||||
cur_offset += 0x04*total_subsongs; /* skip streams info */
|
||||
cur_offset += 0x04*total_subsongs; /* skip streams ids */
|
||||
|
||||
if ((signature == get_id32be("BIK\0") && revision >= 'k') || (signature == get_id32be("KB2\0") && revision >= 'i'))
|
||||
cur_offset += 0x04;
|
||||
|
||||
//if (video_flags & 0x000004) /* only n+? */
|
||||
// cur_offset += 0x0c; /* 6 number: s16 * 0.00003051850944757462 (video stuff?) */
|
||||
//if (video_flags & 0x010000)
|
||||
// cur_offset += 0x18;
|
||||
//if (video_flags & 0x000010) /* only i+? */
|
||||
// cur_offset += 0x04;
|
||||
|
||||
cur_offset += 0x04 * total_subsongs; /* skip streams max packet bytes */
|
||||
sample_rate = read_u16le(cur_offset + 0x04 * (target_subsong - 1) + 0x00, sf);
|
||||
audio_flags = read_u16le(cur_offset + 0x04 * (target_subsong - 1) + 0x02, sf);
|
||||
cur_offset += 0x04 * total_subsongs; /* skip streams info */
|
||||
cur_offset += 0x04 * total_subsongs; /* skip streams ids */
|
||||
|
||||
/* audio flags:
|
||||
- 8000 (bit 15): unknown (observed in some samples)
|
||||
- 4000 (bit 14): unknown (same file may have it set for none/some/all)
|
||||
- 2000 (bit 13): stereo flag
|
||||
- 1000 (bit 12): audio type (1=DCT, 0=FFT)
|
||||
*/
|
||||
channels = audio_flags & 0x2000 ? 2 : 1;
|
||||
|
||||
|
||||
/* read frame offsets in a buffer, to avoid fseeking to the table back and forth */
|
||||
offsets = malloc(sizeof(uint32_t) * num_frames);
|
||||
offsets = malloc(num_frames * sizeof(uint32_t));
|
||||
if (!offsets) goto fail;
|
||||
|
||||
for (i=0; i < num_frames; i++) {
|
||||
offsets[i] = read_32bitLE(cur_offset,sf) & 0xFFFFFFFE; /* mask first bit (= keyframe) */
|
||||
for (int i = 0; i < num_frames; i++) {
|
||||
offsets[i] = read_u32le(cur_offset, sf) & 0xFFFFFFFE; /* mask first bit (= keyframe) */
|
||||
cur_offset += 0x4;
|
||||
|
||||
if (offsets[i] > filesize) goto fail;
|
||||
if (offsets[i] > file_size)
|
||||
goto fail;
|
||||
}
|
||||
/* after the last index is the file size, validate just in case */
|
||||
if (read_32bitLE(cur_offset,sf) != filesize) goto fail;
|
||||
if (read_u32le(cur_offset,sf) != file_size)
|
||||
goto fail;
|
||||
|
||||
/* read each frame header and sum all samples
|
||||
* a frame has N audio packets with a header (one per stream) + video packet */
|
||||
for (i=0; i < num_frames; i++) {
|
||||
for (int i = 0; i < num_frames; i++) {
|
||||
cur_offset = offsets[i];
|
||||
|
||||
/* read audio packet headers per stream */
|
||||
for (j=0; j < total_subsongs; j++) {
|
||||
uint32_t ap_size = read_32bitLE(cur_offset+0x00,sf); /* not counting this int */
|
||||
for (int j = 0; j < total_subsongs; j++) {
|
||||
uint32_t ap_size = read_u32le(cur_offset + 0x00,sf); /* not counting this int */
|
||||
|
||||
if (j == target_subsong-1) {
|
||||
if (j + 1 == target_subsong) {
|
||||
stream_size += 0x04 + ap_size;
|
||||
if (ap_size > 0)
|
||||
num_samples_b += read_32bitLE(cur_offset+0x04,sf); /* decoded samples in bytes */
|
||||
num_samples_b += read_u32le(cur_offset + 0x04,sf); /* decoded samples in bytes */
|
||||
break; /* next frame */
|
||||
}
|
||||
else { /* next stream packet or frame */
|
||||
|
@ -140,8 +188,6 @@ static int bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subson
|
|||
}
|
||||
}
|
||||
|
||||
free(offsets);
|
||||
|
||||
|
||||
if (p_total_subsongs) *p_total_subsongs = total_subsongs;
|
||||
if (p_stream_size) *p_stream_size = stream_size;
|
||||
|
@ -150,9 +196,9 @@ static int bink_get_info(STREAMFILE* sf, int target_subsong, int* p_total_subson
|
|||
//todo returns a few more samples (~48) than binkconv.exe?
|
||||
if (p_num_samples) *p_num_samples = num_samples_b / (2 * channels);
|
||||
|
||||
return 1;
|
||||
|
||||
free(offsets);
|
||||
return true;
|
||||
fail:
|
||||
free(offsets);
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -143,6 +143,11 @@ VGMSTREAM* init_vgmstream_bkhd(STREAMFILE* sf) {
|
|||
vgmstream = init_vgmstream_wwise_bnk(temp_sf, &prefetch);
|
||||
if (!vgmstream) goto fail;
|
||||
}
|
||||
else if (is_id32be(0x00, temp_sf, "ADM3")) {
|
||||
// TODO: these may have multiple subsongs
|
||||
vgmstream = init_vgmstream_adm3(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
}
|
||||
else if (read_f32(subfile_offset + 0x02, temp_sf) >= 30.0 &&
|
||||
read_f32(subfile_offset + 0x02, temp_sf) <= 250.0) {
|
||||
is_wmid = 1;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "../coding/coding.h"
|
||||
#include "../util/endianness.h"
|
||||
|
||||
typedef enum { NONE, DUMMY, PSX, PCM16, ATRAC9, HEVAG, RIFF_ATRAC9 } bnk_codec;
|
||||
typedef enum { NONE, DUMMY, PSX, PCM16, MPEG, ATRAC9, HEVAG, RIFF_ATRAC9 } bnk_codec;
|
||||
|
||||
typedef struct {
|
||||
bnk_codec codec;
|
||||
|
@ -41,6 +41,7 @@ typedef struct {
|
|||
int32_t num_samples;
|
||||
int32_t loop_start;
|
||||
int32_t loop_end;
|
||||
int32_t encoder_delay;
|
||||
|
||||
uint32_t start_offset;
|
||||
uint32_t stream_offset;
|
||||
|
@ -81,7 +82,7 @@ VGMSTREAM* init_vgmstream_bnk_sony(STREAMFILE* sf) {
|
|||
|
||||
vgmstream->meta_type = meta_BNK_SONY;
|
||||
|
||||
if (!h.stream_name_size)
|
||||
if (h.stream_name_size >= STREAM_NAME_SIZE || h.stream_name_size <= 0)
|
||||
h.stream_name_size = STREAM_NAME_SIZE;
|
||||
|
||||
if (!h.bank_name_offset && h.stream_name_offset) {
|
||||
|
@ -90,7 +91,7 @@ VGMSTREAM* init_vgmstream_bnk_sony(STREAMFILE* sf) {
|
|||
else if (h.bank_name_offset && h.stream_name_offset) {
|
||||
read_string(bank_name, h.stream_name_size, h.bank_name_offset, sf);
|
||||
read_string(stream_name, h.stream_name_size, h.stream_name_offset, sf);
|
||||
snprintf(vgmstream->stream_name, h.stream_name_size, "%s/%s", bank_name, stream_name);
|
||||
snprintf(vgmstream->stream_name, h.stream_name_size, "%s%s%s", bank_name, bank_name[0] == '\0' ? "" : "/", stream_name);
|
||||
}
|
||||
|
||||
|
||||
|
@ -147,6 +148,20 @@ VGMSTREAM* init_vgmstream_bnk_sony(STREAMFILE* sf) {
|
|||
return temp_vs;
|
||||
}
|
||||
#endif
|
||||
#ifdef VGM_USE_MPEG
|
||||
case MPEG: {
|
||||
mpeg_custom_config cfg = {0};
|
||||
cfg.skip_samples = h.encoder_delay;
|
||||
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->codec_data = init_mpeg_custom(sf, h.start_offset, &vgmstream->coding_type, h.channels, MPEG_STANDARD, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
|
||||
vgmstream->num_samples = h.num_samples;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
case PCM16:
|
||||
vgmstream->coding_type = h.big_endian ? coding_PCM16BE : coding_PCM16LE;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
|
@ -417,12 +432,12 @@ static bool process_headers(STREAMFILE* sf, bnk_header_t* h) {
|
|||
switch(h->sblk_version) {
|
||||
case 0x01:
|
||||
/* table2/3 has size 0x28 entries, seemingly:
|
||||
* 0x00: subtype(01=sound)
|
||||
* 0x08: same as other versions (pitch, flags, offset...)
|
||||
* rest: padding
|
||||
* 0x18: stream offset
|
||||
* there is no stream size like in v0x03
|
||||
*/
|
||||
* 0x00: subtype(01=sound)
|
||||
* 0x08: same as other versions (pitch, flags, offset...)
|
||||
* rest: padding
|
||||
* 0x18: stream offset
|
||||
* there is no stream size like in v0x03
|
||||
*/
|
||||
|
||||
for (i = 0; i < h->grains_entries; i++) {
|
||||
uint32_t table2_type = read_u32(h->table2_offset + (i*0x28) + 0x00, sf);
|
||||
|
@ -527,22 +542,22 @@ static bool process_headers(STREAMFILE* sf, bnk_header_t* h) {
|
|||
h->sample_rate = (pitch * 48000) / 0x1000;
|
||||
|
||||
/* waves can set base sample rate (48/44/22/11/8khz) + pitch in semitones, then converted to center+fine
|
||||
* 48000 + pitch 0.00 > center=0xc4, fine=0x00
|
||||
* 48000 + pitch 0.10 > center=0xc4, fine=0x0c
|
||||
* 48000 + pitch 0.50 > center=0xc4, fine=0x3f
|
||||
* 48000 + pitch 0.99 > center=0xc4, fine=0x7d
|
||||
* 48000 + pitch 1.00 > center=0xc5, fine=0x00
|
||||
* 48000 + pitch 12.00 > center=0xd0, fine=0x00
|
||||
* 48000 + pitch 24.00 > center=0xdc, fine=0x00
|
||||
* 48000 + pitch 56.00 > center=0xfc, fine=0x00
|
||||
* 48000 + pitch 68.00 > center=0x08, fine=0x00 > ?
|
||||
* 48000 + pitch -12.00 > center=0xb8, fine=0x00
|
||||
* 48000 + pitch -0.10 > center=0xc3, fine=0x72
|
||||
* 48000 + pitch -0.001 > not allowed
|
||||
* 8000 + pitch 1.00 > center=0xa4, fine=0x7c
|
||||
* 8000 + pitch -12.00 > center=0x98, fine=0x7c
|
||||
* 8000 + pitch -48.00 > center=0x74, fine=0x7c
|
||||
*/
|
||||
* 48000 + pitch 0.00 > center=0xc4, fine=0x00
|
||||
* 48000 + pitch 0.10 > center=0xc4, fine=0x0c
|
||||
* 48000 + pitch 0.50 > center=0xc4, fine=0x3f
|
||||
* 48000 + pitch 0.99 > center=0xc4, fine=0x7d
|
||||
* 48000 + pitch 1.00 > center=0xc5, fine=0x00
|
||||
* 48000 + pitch 12.00 > center=0xd0, fine=0x00
|
||||
* 48000 + pitch 24.00 > center=0xdc, fine=0x00
|
||||
* 48000 + pitch 56.00 > center=0xfc, fine=0x00
|
||||
* 48000 + pitch 68.00 > center=0x08, fine=0x00 > ?
|
||||
* 48000 + pitch -12.00 > center=0xb8, fine=0x00
|
||||
* 48000 + pitch -0.10 > center=0xc3, fine=0x72
|
||||
* 48000 + pitch -0.001 > not allowed
|
||||
* 8000 + pitch 1.00 > center=0xa4, fine=0x7c
|
||||
* 8000 + pitch -12.00 > center=0x98, fine=0x7c
|
||||
* 8000 + pitch -48.00 > center=0x74, fine=0x7c
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -567,7 +582,7 @@ static bool process_headers(STREAMFILE* sf, bnk_header_t* h) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
//;VGM_LOG("BNK: stream at %lx + %x\n", h->stream_offset, h->stream_size);
|
||||
;VGM_LOG("BNK: header %x, stream at %x + %x\n", sndh_offset, h->data_offset + h->stream_offset, h->stream_size);
|
||||
|
||||
return true;
|
||||
fail:
|
||||
|
@ -602,17 +617,17 @@ static bool process_names(STREAMFILE* sf, bnk_header_t* h) {
|
|||
}
|
||||
|
||||
/* table4:
|
||||
* 0x00: bank name (optional)
|
||||
* 0x08: name section offset
|
||||
* 0x0C-0x14: 3 null pointers (reserved?)
|
||||
* 0x18-0x58: 32 name chunk offset indices
|
||||
*/
|
||||
* 0x00: bank name (optional)
|
||||
* 0x08: name section offset
|
||||
* 0x0C-0x18: 3 null pointers (reserved?)
|
||||
* 0x18-0x58: 32 name chunk offset indices
|
||||
*/
|
||||
|
||||
/* Name chunks are organised as
|
||||
* (name[0] + name[4] + name[8] + name[12]) & 0x1F;
|
||||
* and using that as the index for the chunk offsets
|
||||
* name_sect_offset + (chunk_idx[result] * 0x14);
|
||||
*/
|
||||
* (name[0] + name[4] + name[8] + name[12]) % 32;
|
||||
* and using that as the index for the chunk offsets
|
||||
* name_sect_offset + (chunk_idx[result] * 0x14);
|
||||
*/
|
||||
if (read_u8(h->table4_offset, sf))
|
||||
h->bank_name_offset = h->table4_offset;
|
||||
|
||||
|
@ -626,7 +641,7 @@ static bool process_names(STREAMFILE* sf, bnk_header_t* h) {
|
|||
while (read_u8(h->stream_name_offset, sf)) {
|
||||
/* in case it goes somewhere out of bounds unexpectedly */
|
||||
if (((read_u8(h->stream_name_offset + 0x00, sf) + read_u8(h->stream_name_offset + 0x04, sf) +
|
||||
read_u8(h->stream_name_offset + 0x08, sf) + read_u8(h->stream_name_offset + 0x0C, sf)) & 0x1F) != i)
|
||||
read_u8(h->stream_name_offset + 0x08, sf) + read_u8(h->stream_name_offset + 0x0C, sf)) & 0x1F) != i)
|
||||
goto fail;
|
||||
if (read_u16(h->stream_name_offset + 0x10, sf) == table4_entry_id)
|
||||
goto loop_break; /* to break out of the for+while loop simultaneously */
|
||||
|
@ -653,16 +668,16 @@ static bool process_names(STREAMFILE* sf, bnk_header_t* h) {
|
|||
}
|
||||
|
||||
/* table4:
|
||||
* 0x00: bank name (optional)
|
||||
* 0x08: name entries offset
|
||||
* 0x0C: name section offset
|
||||
*
|
||||
* name entries offset:
|
||||
* 0x00: name offset in name section
|
||||
* 0x04: name hash(?)
|
||||
* 0x08: ? (2x int16)
|
||||
* 0x0C: section index (int16)
|
||||
*/
|
||||
* 0x00: bank name (optional)
|
||||
* 0x08: name entries offset
|
||||
* 0x0C: name section offset
|
||||
*
|
||||
* name entries offset:
|
||||
* 0x00: name offset in name section
|
||||
* 0x04: name hash(?)
|
||||
* 0x08: ? (2x int16)
|
||||
* 0x0C: section index (int16)
|
||||
*/
|
||||
if (read_u8(h->table4_offset, sf))
|
||||
h->bank_name_offset = h->table4_offset;
|
||||
|
||||
|
@ -722,7 +737,7 @@ static bool process_names(STREAMFILE* sf, bnk_header_t* h) {
|
|||
break;
|
||||
}
|
||||
|
||||
//;VGM_LOG("BNK: stream_offset=%lx, stream_size=%x, stream_name_offset=%lx\n", h->stream_offset, h->stream_size, h->stream_name_offset);
|
||||
//;VGM_LOG("BNK: stream_offset=%x, stream_size=%x, stream_name_offset=%x\n", h->stream_offset, h->stream_size, h->stream_name_offset);
|
||||
|
||||
return true;
|
||||
fail:
|
||||
|
@ -785,19 +800,19 @@ static bool process_data(STREAMFILE* sf, bnk_header_t* h) {
|
|||
h->interleave = h->stream_size / h->channels;
|
||||
|
||||
/* PS Home Arcade has other flags? supposedly:
|
||||
* 01 = reverb
|
||||
* 02 = vol scale 20
|
||||
* 04 = vol scale 50
|
||||
* 06 = vol scale 100
|
||||
* 08 = noise
|
||||
* 10 = no dry
|
||||
* 20 = no steal
|
||||
* 40 = loop VAG
|
||||
* 80 = PCM
|
||||
* 100 = has advanced packets
|
||||
* 200 = send LFE
|
||||
* 400 = send center
|
||||
*/
|
||||
* 01 = reverb
|
||||
* 02 = vol scale 20
|
||||
* 04 = vol scale 50
|
||||
* 06 = vol scale 100
|
||||
* 08 = noise
|
||||
* 10 = no dry
|
||||
* 20 = no steal
|
||||
* 40 = loop VAG
|
||||
* 80 = PCM
|
||||
* 100 = has advanced packets
|
||||
* 200 = send LFE
|
||||
* 400 = send center
|
||||
*/
|
||||
if ((h->stream_flags & 0x80) && h->sblk_version <= 3) {
|
||||
h->codec = PCM16; /* rare [Wipeout HD (PS3)]-v3 */
|
||||
}
|
||||
|
@ -813,8 +828,8 @@ static bool process_data(STREAMFILE* sf, bnk_header_t* h) {
|
|||
|
||||
case 0x08:
|
||||
case 0x09:
|
||||
subtype = read_u16(h->start_offset+0x00,sf);
|
||||
extradata_size = 0x08 + read_u32(h->start_offset+0x04,sf); /* 0x14 for AT9 */
|
||||
subtype = read_u32(h->start_offset+0x00,sf);
|
||||
extradata_size = 0x08 + read_u32(h->start_offset+0x04,sf); /* 0x14 for AT9, 0x10 for PCM, 0x90 for MPEG */
|
||||
|
||||
switch(subtype) {
|
||||
case 0x00:
|
||||
|
@ -829,22 +844,49 @@ static bool process_data(STREAMFILE* sf, bnk_header_t* h) {
|
|||
h->interleave = 0x01;
|
||||
break;
|
||||
|
||||
|
||||
case 0x02: /* ATRAC9 mono */
|
||||
case 0x05: /* ATRAC9 stereo */
|
||||
if (read_u32(h->start_offset+0x08,sf) + 0x08 != extradata_size) { /* repeat? */
|
||||
VGM_LOG("BNK: unknown subtype\n");
|
||||
goto fail;
|
||||
}
|
||||
case 0x02: /* ATRAC9 / MPEG mono */
|
||||
case 0x05: /* ATRAC9 / MPEG stereo */
|
||||
h->channels = (subtype == 0x02) ? 1 : 2;
|
||||
|
||||
h->atrac9_info = read_u32be(h->start_offset+0x0c,sf);
|
||||
/* 0x10: null? */
|
||||
loop_length = read_u32(h->start_offset+0x14,sf);
|
||||
h->loop_start = read_u32(h->start_offset+0x18,sf);
|
||||
h->loop_end = h->loop_start + loop_length; /* loop_start is -1 if not set */
|
||||
if (h->big_endian) {
|
||||
/* The Last of Us demo (PS3) */
|
||||
|
||||
h->codec = ATRAC9;
|
||||
/* 0x08: mpeg version? (1) */
|
||||
/* 0x0C: mpeg layer? (3) */
|
||||
/* 0x10: ? (related to frame size, 0xC0 > 0x40, 0x120 > 0x60) */
|
||||
/* 0x14: sample rate */
|
||||
/* 0x18: mpeg layer? (3) */
|
||||
/* 0x1c: mpeg version? (1) */
|
||||
/* 0x20: channels? */
|
||||
/* 0x24: frame size */
|
||||
/* 0x28: encoder delay */
|
||||
/* 0x2c: num samples */
|
||||
/* 0x30: ? */
|
||||
/* 0x34: ? */
|
||||
/* 0x38: 0? */
|
||||
/* 0x3c: data size */
|
||||
/* padding up to 0x90 */
|
||||
|
||||
h->encoder_delay = read_s32(h->start_offset+0x28,sf);
|
||||
h->num_samples = read_s32(h->start_offset+0x2c,sf);
|
||||
|
||||
h->codec = MPEG;
|
||||
}
|
||||
else {
|
||||
/* Puyo Puyo Tetris (PS4) */
|
||||
if (read_u32(h->start_offset+0x08,sf) + 0x08 != extradata_size) { /* repeat? */
|
||||
VGM_LOG("BNK: unknown subtype\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
h->atrac9_info = read_u32be(h->start_offset+0x0c,sf);
|
||||
/* 0x10: null? */
|
||||
loop_length = read_u32(h->start_offset+0x14,sf);
|
||||
h->loop_start = read_u32(h->start_offset+0x18,sf);
|
||||
h->loop_end = h->loop_start + loop_length; /* loop_start is -1 if not set */
|
||||
|
||||
h->codec = ATRAC9;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
46
Frameworks/vgmstream/vgmstream/src/meta/cbx.c
Normal file
46
Frameworks/vgmstream/vgmstream/src/meta/cbx.c
Normal file
|
@ -0,0 +1,46 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* !B0X - Traveller's Tales speech files [Lego Batman 2 (PC), Lego Dimensions (PS3)] */
|
||||
VGMSTREAM* init_vgmstream_cbx(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
uint32_t start_offset, pcm_size;
|
||||
int loop_flag, channels, sample_rate;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "!B0X"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf, "cbx"))
|
||||
return NULL;
|
||||
|
||||
/* debug strings identify this as "Chatterbox"/"CBOX"/"CBX", while sound lib seems called "NuSound"
|
||||
* (probably based on .utk) */
|
||||
|
||||
pcm_size = read_u32le(0x04, sf);
|
||||
sample_rate = read_s32le(0x08, sf);
|
||||
start_offset = 0x0c;
|
||||
channels = 1;
|
||||
loop_flag = 0;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_CBX;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = pcm_size / 2;
|
||||
vgmstream->coding_type = coding_EA_MT;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->codec_data = init_ea_mt_cbx(vgmstream->channels);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -282,11 +282,11 @@ static void load_cpk_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstre
|
|||
return;
|
||||
|
||||
/* companion .acb probably loaded */
|
||||
load_acb_wave_name(sf_acb, vgmstream, waveid, port, is_memory);
|
||||
load_acb_wave_info(sf_acb, vgmstream, waveid, port, is_memory, 0);
|
||||
|
||||
close_streamfile(sf_acb);
|
||||
}
|
||||
else {
|
||||
load_acb_wave_name(sf_acb, vgmstream, waveid, port, is_memory);
|
||||
load_acb_wave_info(sf_acb, vgmstream, waveid, port, is_memory, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
#include "meta.h"
|
||||
#include "../util.h"
|
||||
|
||||
/* ASD - found in Miss Moonlight (DC) */
|
||||
VGMSTREAM * init_vgmstream_dc_asd(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
char filename[PATH_LIMIT];
|
||||
off_t start_offset;
|
||||
int loop_flag;
|
||||
int channel_count;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
if (strcasecmp("asd",filename_extension(filename))) goto fail;
|
||||
|
||||
/* We have no "Magic" words in this header format, so we have to do some,
|
||||
other checks, it seems the samplecount is stored twice in the header,
|
||||
we'll compare it... */
|
||||
if (read_32bitLE(0x0,streamFile) != read_32bitLE(0x4,streamFile))
|
||||
goto fail;
|
||||
/* compare the frequency with the bitrate, if it doesn't match we'll close
|
||||
the vgmstream... */
|
||||
if (read_32bitLE(0x10,streamFile)/read_32bitLE(0xC,streamFile) != (uint16_t)read_16bitLE(0xA,streamFile)*2)
|
||||
goto fail;
|
||||
|
||||
loop_flag = 0;
|
||||
channel_count = read_16bitLE(0x0A,streamFile);
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* fill in the vital statistics */
|
||||
start_offset = get_streamfile_size(streamFile) - read_32bitLE(0x0,streamFile);
|
||||
vgmstream->channels = channel_count;
|
||||
vgmstream->sample_rate = read_32bitLE(0x0C,streamFile);
|
||||
vgmstream->coding_type = coding_PCM16LE;
|
||||
vgmstream->num_samples = read_32bitLE(0x0,streamFile)/2/channel_count;
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = 0;
|
||||
vgmstream->loop_end_sample = read_32bitLE(0x0,streamFile)/2/channel_count;
|
||||
}
|
||||
|
||||
vgmstream->meta_type = meta_DC_ASD;
|
||||
|
||||
if (vgmstream->channels == 1) {
|
||||
vgmstream->layout_type = layout_none;
|
||||
} else if (vgmstream->channels == 2) {
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x2;
|
||||
}
|
||||
|
||||
/* open the file for reading */
|
||||
{
|
||||
int i;
|
||||
STREAMFILE * file;
|
||||
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (!file) goto fail;
|
||||
for (i=0;i<channel_count;i++) {
|
||||
vgmstream->ch[i].streamfile = file;
|
||||
|
||||
vgmstream->ch[i].channel_start_offset=
|
||||
vgmstream->ch[i].offset=start_offset+
|
||||
vgmstream->interleave_block_size*i;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
File diff suppressed because it is too large
Load diff
197
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_abk.c
Normal file
197
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_abk.c
Normal file
|
@ -0,0 +1,197 @@
|
|||
#include "meta.h"
|
||||
#include "../util/endianness.h"
|
||||
|
||||
static VGMSTREAM* parse_s10a_header(STREAMFILE* sf, off_t offset, uint16_t target_index, off_t ast_offset);
|
||||
|
||||
|
||||
/* EA ABK - ABK header seems to be same as in the old games but the sound table is different and it contains SNR/SNS sounds instead */
|
||||
VGMSTREAM* init_vgmstream_ea_abk_eaac(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream;
|
||||
int is_dupe, total_sounds = 0, target_stream = sf->stream_index;
|
||||
off_t bnk_offset, modules_table, module_data, player_offset, samples_table, entry_offset, ast_offset;
|
||||
off_t cfg_num_players_off, cfg_module_data_off, cfg_module_entry_size, cfg_samples_table_off;
|
||||
uint32_t num_sounds, num_sample_tables;
|
||||
uint16_t num_modules, bnk_index, bnk_target_index;
|
||||
uint8_t num_players;
|
||||
off_t sample_tables[0x400];
|
||||
int32_t(*read_32bit)(off_t, STREAMFILE*);
|
||||
int16_t(*read_16bit)(off_t, STREAMFILE*);
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00, sf, "ABKC"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf, "abk"))
|
||||
return NULL;
|
||||
|
||||
/* use table offset to check endianness */
|
||||
if (guess_endian32(0x1C, sf)) {
|
||||
read_32bit = read_32bitBE;
|
||||
read_16bit = read_16bitBE;
|
||||
} else {
|
||||
read_32bit = read_32bitLE;
|
||||
read_16bit = read_16bitLE;
|
||||
}
|
||||
|
||||
if (target_stream == 0) target_stream = 1;
|
||||
if (target_stream < 0)
|
||||
goto fail;
|
||||
|
||||
num_modules = read_16bit(0x0A, sf);
|
||||
modules_table = read_32bit(0x1C, sf);
|
||||
bnk_offset = read_32bit(0x20, sf);
|
||||
num_sample_tables = 0;
|
||||
bnk_target_index = 0xFFFF;
|
||||
ast_offset = 0;
|
||||
|
||||
if (!bnk_offset || !is_id32be(bnk_offset, sf, "S10A"))
|
||||
goto fail;
|
||||
|
||||
/* set up some common values */
|
||||
if (modules_table == 0x5C) {
|
||||
/* the usual variant */
|
||||
cfg_num_players_off = 0x24;
|
||||
cfg_module_data_off = 0x2C;
|
||||
cfg_module_entry_size = 0x3C;
|
||||
cfg_samples_table_off = 0x04;
|
||||
}
|
||||
else if (modules_table == 0x78) {
|
||||
/* FIFA 08 has a bunch of extra zeroes all over the place, don't know what's up with that */
|
||||
cfg_num_players_off = 0x40;
|
||||
cfg_module_data_off = 0x54;
|
||||
cfg_module_entry_size = 0x68;
|
||||
cfg_samples_table_off = 0x0C;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < num_modules; i++) {
|
||||
num_players = read_8bit(modules_table + cfg_num_players_off, sf);
|
||||
module_data = read_32bit(modules_table + cfg_module_data_off, sf);
|
||||
if (num_players == 0xff) goto fail; /* EOF read */
|
||||
|
||||
for (uint32_t j = 0; j < num_players; j++) {
|
||||
player_offset = read_32bit(modules_table + cfg_module_entry_size + 0x04 * j, sf);
|
||||
samples_table = read_32bit(module_data + player_offset + cfg_samples_table_off, sf);
|
||||
|
||||
/* multiple players may point at the same sound table */
|
||||
is_dupe = 0;
|
||||
for (uint32_t k = 0; k < num_sample_tables; k++) {
|
||||
if (samples_table == sample_tables[k]) {
|
||||
is_dupe = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_dupe)
|
||||
continue;
|
||||
|
||||
sample_tables[num_sample_tables++] = samples_table;
|
||||
num_sounds = read_32bit(samples_table, sf);
|
||||
if (num_sounds == 0xffffffff) goto fail; /* EOF read */
|
||||
|
||||
for (uint32_t k = 0; k < num_sounds; k++) {
|
||||
/* 0x00: sound index */
|
||||
/* 0x02: priority */
|
||||
/* 0x03: azimuth */
|
||||
/* 0x08: streamed data offset */
|
||||
entry_offset = samples_table + 0x04 + 0x0C * k;
|
||||
bnk_index = read_16bit(entry_offset + 0x00, sf);
|
||||
|
||||
/* some of these are dummies */
|
||||
if (bnk_index == 0xFFFF)
|
||||
continue;
|
||||
|
||||
total_sounds++;
|
||||
if (target_stream == total_sounds) {
|
||||
bnk_target_index = bnk_index;
|
||||
ast_offset = read_32bit(entry_offset + 0x08, sf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* skip class controllers */
|
||||
num_players += read_8bit(modules_table + cfg_num_players_off + 0x03, sf);
|
||||
modules_table += cfg_module_entry_size + num_players * 0x04;
|
||||
}
|
||||
|
||||
if (bnk_target_index == 0xFFFF || ast_offset == 0)
|
||||
goto fail;
|
||||
|
||||
vgmstream = parse_s10a_header(sf, bnk_offset, bnk_target_index, ast_offset);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->num_streams = total_sounds;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* EA S10A header - seen inside new ABK files. Putting it here in case it's encountered stand-alone. */
|
||||
static VGMSTREAM* parse_s10a_header(STREAMFILE* sf, off_t offset, uint16_t target_index, off_t sns_offset) {
|
||||
VGMSTREAM* vgmstream;
|
||||
STREAMFILE *sf_ast = NULL;
|
||||
uint32_t num_sounds;
|
||||
off_t snr_offset;
|
||||
eaac_meta_t info = {0};
|
||||
|
||||
/* header is always big endian */
|
||||
/* 0x00: header magic */
|
||||
/* 0x04: version */
|
||||
/* 0x05: padding */
|
||||
/* 0x06: serial number */
|
||||
/* 0x08: number of files */
|
||||
/* 0x0C: offsets table */
|
||||
if (!is_id32be(offset + 0x00, sf, "S10A"))
|
||||
return NULL;
|
||||
|
||||
num_sounds = read_32bitBE(offset + 0x08, sf);
|
||||
if (num_sounds == 0 || target_index >= num_sounds)
|
||||
return NULL;
|
||||
|
||||
snr_offset = offset + read_32bitBE(offset + 0x0C + 0x04 * target_index, sf);
|
||||
|
||||
if (sns_offset == 0xFFFFFFFF) {
|
||||
/* RAM asset */
|
||||
//;VGM_LOG("EA S10A: RAM at snr=%lx", snr_offset);
|
||||
|
||||
info.sf_head = sf;
|
||||
info.head_offset = snr_offset;
|
||||
info.body_offset = 0x00;
|
||||
info.type = meta_EA_SNR_SNS;
|
||||
|
||||
vgmstream = load_vgmstream_ea_eaac(&info);
|
||||
if (!vgmstream) goto fail;
|
||||
}
|
||||
else {
|
||||
/* streamed asset */
|
||||
sf_ast = open_streamfile_by_ext(sf, "ast");
|
||||
if (!sf_ast) {
|
||||
vgm_logi("EA ABK: .ast file not found (find and put together)\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!is_id32be(0x00, sf_ast, "S10S"))
|
||||
goto fail;
|
||||
|
||||
//;VGM_LOG("EA S10A: stream at snr=%lx, sns=%lx\n", snr_offset, sns_offset);
|
||||
|
||||
info.sf_head = sf;
|
||||
info.sf_body = sf_ast;
|
||||
info.head_offset = snr_offset;
|
||||
info.body_offset = sns_offset;
|
||||
info.type = meta_EA_SNR_SNS;
|
||||
|
||||
vgmstream = load_vgmstream_ea_eaac(&info);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
close_streamfile(sf_ast);
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_streamfile(sf_ast);
|
||||
return NULL;
|
||||
}
|
160
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_hdr_sth_dat.c
Normal file
160
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_hdr_sth_dat.c
Normal file
|
@ -0,0 +1,160 @@
|
|||
#include "meta.h"
|
||||
#include "../util/endianness.h"
|
||||
|
||||
|
||||
#define EAAC_BLOCKID0_DATA 0x00
|
||||
#define EAAC_BLOCKID0_END 0x80 /* maybe meant to be a bitflag? */
|
||||
|
||||
|
||||
/* EA HDR/STH/DAT - seen in older 7th gen games, used for storing speech */
|
||||
VGMSTREAM* init_vgmstream_ea_hdr_sth_dat(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream;
|
||||
STREAMFILE *sf_dat = NULL, *sf_sth = NULL;
|
||||
int target_stream = sf->stream_index;
|
||||
uint32_t snr_offset, sns_offset, block_size;
|
||||
uint16_t sth_offset, sth_offset2;
|
||||
uint8_t num_params, num_sounds, block_id;
|
||||
size_t dat_size;
|
||||
uint32_t(*read_u32)(off_t, STREAMFILE*);
|
||||
eaac_meta_t info = {0};
|
||||
|
||||
/* 0x00: ID */
|
||||
/* 0x02: number of parameters */
|
||||
/* 0x03: number of samples */
|
||||
/* 0x04: speaker ID (used for different police voices in NFS games) */
|
||||
/* 0x08: sample repeat (alt number of samples?) */
|
||||
/* 0x09: block size (always zero?) */
|
||||
/* 0x0a: number of blocks (related to size?) */
|
||||
/* 0x0c: number of sub-banks (always zero?) */
|
||||
/* 0x0e: padding */
|
||||
/* 0x10: table start */
|
||||
|
||||
if (!check_extensions(sf, "hdr"))
|
||||
return NULL;
|
||||
|
||||
if (read_u8(0x09, sf) != 0)
|
||||
return NULL;
|
||||
|
||||
if (read_u32be(0x0c, sf) != 0)
|
||||
return NULL;
|
||||
|
||||
/* first offset is always zero */
|
||||
if (read_u16be(0x10, sf) != 0)
|
||||
return NULL;
|
||||
|
||||
sf_sth = open_streamfile_by_ext(sf, "sth");
|
||||
if (!sf_sth) goto fail;
|
||||
|
||||
sf_dat = open_streamfile_by_ext(sf, "dat");
|
||||
if (!sf_dat) goto fail;
|
||||
|
||||
/* STH always starts with the first offset of zero */
|
||||
sns_offset = read_u32be(0x00, sf_sth);
|
||||
if (sns_offset != 0)
|
||||
goto fail;
|
||||
|
||||
/* check if DAT starts with a correct SNS block */
|
||||
block_id = read_u8(0x00, sf_dat);
|
||||
if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END)
|
||||
goto fail;
|
||||
|
||||
num_params = read_u8(0x02, sf) & 0x7F;
|
||||
num_sounds = read_u8(0x03, sf);
|
||||
|
||||
if (read_u8(0x08, sf) > num_sounds)
|
||||
goto fail;
|
||||
|
||||
if (target_stream == 0) target_stream = 1;
|
||||
if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
|
||||
goto fail;
|
||||
|
||||
/* offsets in HDR are always big endian */
|
||||
sth_offset = read_u16be(0x10 + (0x02 + num_params) * (target_stream - 1), sf);
|
||||
|
||||
#if 0
|
||||
snr_offset = sth_offset + 0x04;
|
||||
sns_offset = read_u32(sth_offset + 0x00, sf_sth);
|
||||
#else
|
||||
/* overly intricate way to detect byte endianness because of the simplicity of HDR format */
|
||||
dat_size = get_streamfile_size(sf_dat);
|
||||
snr_offset = 0;
|
||||
sns_offset = 0;
|
||||
|
||||
if (num_sounds == 1) {
|
||||
/* always 0 */
|
||||
snr_offset = sth_offset + 0x04;
|
||||
sns_offset = 0x00;
|
||||
}
|
||||
else {
|
||||
/* find the first sound size and match it up with the second sound offset to detect endianness */
|
||||
while (1) {
|
||||
if (sns_offset >= dat_size)
|
||||
goto fail;
|
||||
|
||||
block_id = read_u8(sns_offset, sf_dat);
|
||||
block_size = read_u32be(sns_offset, sf_dat) & 0x00FFFFFF;
|
||||
if (block_size == 0)
|
||||
goto fail;
|
||||
|
||||
if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END)
|
||||
goto fail;
|
||||
|
||||
sns_offset += block_size;
|
||||
|
||||
if (block_id == EAAC_BLOCKID0_END)
|
||||
break;
|
||||
}
|
||||
|
||||
sns_offset = align_size_to_block(sns_offset, 0x40);
|
||||
sth_offset2 = read_u16be(0x10 + (0x02 + num_params) * 1, sf);
|
||||
if (sns_offset == read_u32be(sth_offset2, sf_sth)) {
|
||||
read_u32 = read_u32be;
|
||||
}
|
||||
else if (sns_offset == read_u32le(sth_offset2, sf_sth)) {
|
||||
read_u32 = read_u32le;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snr_offset = sth_offset + 0x04;
|
||||
sns_offset = read_u32(sth_offset + 0x00, sf_sth);
|
||||
}
|
||||
#endif
|
||||
|
||||
block_id = read_u8(sns_offset, sf_dat);
|
||||
if (block_id != EAAC_BLOCKID0_DATA && block_id != EAAC_BLOCKID0_END)
|
||||
goto fail;
|
||||
|
||||
info.sf_head = sf_sth;
|
||||
info.sf_body = sf_dat;
|
||||
info.head_offset = snr_offset;
|
||||
info.body_offset = sns_offset;
|
||||
info.type = meta_EA_SNR_SNS;
|
||||
|
||||
vgmstream = load_vgmstream_ea_eaac(&info);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
if (num_params != 0) {
|
||||
uint8_t val;
|
||||
char buf[8];
|
||||
int i;
|
||||
for (i = 0; i < num_params; i++) {
|
||||
val = read_u8(0x10 + (0x02 + num_params) * (target_stream - 1) + 0x02 + i, sf);
|
||||
snprintf(buf, sizeof(buf), "%u", val);
|
||||
concatn(STREAM_NAME_SIZE, vgmstream->stream_name, buf);
|
||||
if (i != num_params - 1)
|
||||
concatn(STREAM_NAME_SIZE, vgmstream->stream_name, ", ");
|
||||
}
|
||||
}
|
||||
|
||||
vgmstream->num_streams = num_sounds;
|
||||
close_streamfile(sf_sth);
|
||||
close_streamfile(sf_dat);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(sf_sth);
|
||||
close_streamfile(sf_dat);
|
||||
return NULL;
|
||||
}
|
343
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_mpf_mus.c
Normal file
343
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_mpf_mus.c
Normal file
|
@ -0,0 +1,343 @@
|
|||
#include "meta.h"
|
||||
#include "../util/endianness.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "../util/companion_files.h"
|
||||
|
||||
|
||||
static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track /*, int num_tracks*/);
|
||||
|
||||
|
||||
/* EA MPF/MUS combo - used in older 7th gen games for storing interactive music */
|
||||
VGMSTREAM* init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE *sf_mus = NULL;
|
||||
uint32_t num_tracks, track_start, track_checksum = 0, mus_sounds, mus_stream = 0, bnk_index = 0, bnk_sound_index = 0,
|
||||
tracks_table, samples_table, eof_offset, table_offset, entry_offset = 0, snr_offset, sns_offset;
|
||||
uint16_t num_subbanks, index, sub_index;
|
||||
uint8_t version, sub_version;
|
||||
segmented_layout_data* data_s = NULL;
|
||||
int i;
|
||||
int target_stream = sf->stream_index, total_streams, is_ram = 0;
|
||||
uint32_t(*read_u32)(off_t, STREAMFILE *);
|
||||
uint16_t(*read_u16)(off_t, STREAMFILE *);
|
||||
|
||||
/* checks */
|
||||
if (is_id32be(0x00, sf, "PFDx")) {
|
||||
read_u32 = read_u32be;
|
||||
read_u16 = read_u16be;
|
||||
}
|
||||
else if (is_id32le(0x00, sf, "PFDx")) {
|
||||
read_u32 = read_u32le;
|
||||
read_u16 = read_u16le;
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!check_extensions(sf, "mpf"))
|
||||
return NULL;
|
||||
|
||||
version = read_u8(0x04, sf);
|
||||
sub_version = read_u8(0x05, sf);
|
||||
if (version != 5 || sub_version < 2 || sub_version > 3) goto fail;
|
||||
|
||||
num_tracks = read_u8(0x0d, sf);
|
||||
|
||||
tracks_table = read_u32(0x2c, sf);
|
||||
samples_table = read_u32(0x34, sf);
|
||||
eof_offset = read_u32(0x38, sf);
|
||||
total_streams = (eof_offset - samples_table) / 0x08;
|
||||
|
||||
if (target_stream == 0) target_stream = 1;
|
||||
if (target_stream < 0 || total_streams == 0 || target_stream > total_streams)
|
||||
goto fail;
|
||||
|
||||
for (i = num_tracks - 1; i >= 0; i--) {
|
||||
entry_offset = read_u32(tracks_table + i * 0x04, sf) * 0x04;
|
||||
track_start = read_u32(entry_offset + 0x00, sf);
|
||||
|
||||
if (track_start == 0 && i != 0)
|
||||
continue; /* empty track */
|
||||
|
||||
if (track_start <= target_stream - 1) {
|
||||
num_subbanks = read_u16(entry_offset + 0x04, sf);
|
||||
track_checksum = read_u32be(entry_offset + 0x08, sf);
|
||||
is_ram = (num_subbanks != 0);
|
||||
mus_stream = target_stream - 1 - track_start;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* open MUS file that matches this track */
|
||||
sf_mus = open_mapfile_pair(sf, i);//, num_tracks
|
||||
if (!sf_mus) goto fail;
|
||||
|
||||
/* sample offsets table is still there but it just holds SNS offsets, we only need it for RAM sound indexes */
|
||||
/* 0x00 - offset/index, 0x04 - duration (in milliseconds) */
|
||||
sns_offset = read_u32(samples_table + (target_stream - 1) * 0x08 + 0x00, sf);
|
||||
|
||||
if (is_ram) {
|
||||
bnk_sound_index = (sns_offset & 0x0000FFFF);
|
||||
bnk_index = (sns_offset & 0xFFFF0000) >> 16;
|
||||
|
||||
if (bnk_index != 0) {
|
||||
/* HACK: open proper .mus now since open_mapfile_pair doesn't let us adjust the name */
|
||||
char filename[PATH_LIMIT], basename[PATH_LIMIT], ext[32];
|
||||
int basename_len;
|
||||
STREAMFILE* sf_temp;
|
||||
|
||||
get_streamfile_basename(sf_mus, basename, PATH_LIMIT);
|
||||
basename_len = strlen(basename);
|
||||
get_streamfile_ext(sf_mus, ext, sizeof(ext));
|
||||
|
||||
/* strip off 0 at the end */
|
||||
basename[basename_len - 1] = '\0';
|
||||
|
||||
/* append bank index to the name */
|
||||
snprintf(filename, PATH_LIMIT, "%s%u.%s", basename, bnk_index, ext);
|
||||
|
||||
sf_temp = open_streamfile_by_filename(sf_mus, filename);
|
||||
if (!sf_temp) goto fail;
|
||||
close_streamfile(sf_mus);
|
||||
sf_mus = sf_temp;
|
||||
}
|
||||
|
||||
track_checksum = read_u32be(entry_offset + 0x14 + bnk_index * 0x10, sf);
|
||||
if (track_checksum && read_u32be(0x00, sf_mus) != track_checksum)
|
||||
goto fail;
|
||||
}
|
||||
else {
|
||||
if (track_checksum && read_u32be(0x00, sf_mus) != track_checksum)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* MUS file has a header, however */
|
||||
if (sub_version == 2) {
|
||||
if (read_u32(0x04, sf_mus) != 0x00)
|
||||
goto fail;
|
||||
|
||||
/*
|
||||
* 0x00: index
|
||||
* 0x02: sub-index
|
||||
* 0x04: SNR offset
|
||||
* 0x08: SNS offset (contains garbage for RAM sounds)
|
||||
*/
|
||||
table_offset = 0x08;
|
||||
|
||||
if (is_ram) {
|
||||
int ram_segments;
|
||||
|
||||
/* find number of parts for this node */
|
||||
for (i = 0; ; i++) {
|
||||
entry_offset = table_offset + (bnk_sound_index + i) * 0x0c;
|
||||
index = read_u16(entry_offset + 0x00, sf_mus);
|
||||
sub_index = read_u16(entry_offset + 0x02, sf_mus);
|
||||
|
||||
if (index == 0xffff) /* EOF check */
|
||||
goto fail;
|
||||
|
||||
entry_offset += 0x0c;
|
||||
if (read_u16(entry_offset + 0x00, sf_mus) != index ||
|
||||
read_u16(entry_offset + 0x02, sf_mus) != sub_index + 1)
|
||||
break;
|
||||
}
|
||||
|
||||
ram_segments = i + 1;
|
||||
|
||||
/* init layout */
|
||||
data_s = init_layout_segmented(ram_segments);
|
||||
if (!data_s) goto fail;
|
||||
|
||||
for (i = 0; i < ram_segments; i++) {
|
||||
eaac_meta_t info = {0};
|
||||
|
||||
entry_offset = table_offset + (bnk_sound_index + i) * 0x0c;
|
||||
snr_offset = read_u32(entry_offset + 0x04, sf_mus);
|
||||
|
||||
info.sf_head = sf_mus;
|
||||
info.head_offset = snr_offset;
|
||||
info.body_offset = 0x00;
|
||||
info.type = meta_EA_SNR_SNS;
|
||||
|
||||
data_s->segments[i] = load_vgmstream_ea_eaac(&info);
|
||||
if (!data_s->segments[i]) goto fail;
|
||||
}
|
||||
|
||||
/* setup segmented VGMSTREAMs */
|
||||
if (!setup_layout_segmented(data_s))
|
||||
goto fail;
|
||||
vgmstream = allocate_segmented_vgmstream(data_s, 0, 0, 0);
|
||||
}
|
||||
else {
|
||||
eaac_meta_t info = {0};
|
||||
|
||||
entry_offset = table_offset + mus_stream * 0x0c;
|
||||
snr_offset = read_u32(entry_offset + 0x04, sf_mus);
|
||||
sns_offset = read_u32(entry_offset + 0x08, sf_mus);
|
||||
|
||||
info.sf_head = sf_mus;
|
||||
info.sf_body = sf_mus;
|
||||
info.head_offset = snr_offset;
|
||||
info.body_offset = sns_offset;
|
||||
info.type = meta_EA_SNR_SNS;
|
||||
|
||||
vgmstream = load_vgmstream_ea_eaac(&info);
|
||||
}
|
||||
}
|
||||
else if (sub_version == 3) {
|
||||
eaac_meta_t info = {0};
|
||||
|
||||
/* number of samples is always little endian */
|
||||
mus_sounds = read_u32le(0x04, sf_mus);
|
||||
if (mus_stream >= mus_sounds)
|
||||
goto fail;
|
||||
|
||||
if (is_ram) {
|
||||
/* not seen so far */
|
||||
VGM_LOG("EA EAAC MUS: found RAM track in MPF v5.3.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/*
|
||||
* 0x00: checksum
|
||||
* 0x04: index
|
||||
* 0x06: sub-index
|
||||
* 0x08: SNR offset
|
||||
* 0x0c: SNS offset
|
||||
* 0x10: SNR size
|
||||
* 0x14: SNS size
|
||||
* 0x18: zero
|
||||
*/
|
||||
table_offset = 0x28;
|
||||
entry_offset = table_offset + mus_stream * 0x1c;
|
||||
snr_offset = read_u32(entry_offset + 0x08, sf_mus) * 0x10;
|
||||
sns_offset = read_u32(entry_offset + 0x0c, sf_mus) * 0x80;
|
||||
|
||||
info.sf_head = sf_mus;
|
||||
info.sf_body = sf_mus;
|
||||
info.head_offset = snr_offset;
|
||||
info.body_offset = sns_offset;
|
||||
info.type = meta_EA_SNR_SNS;
|
||||
|
||||
vgmstream = load_vgmstream_ea_eaac(&info);
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!vgmstream)
|
||||
goto fail;
|
||||
|
||||
vgmstream->num_streams = total_streams;
|
||||
get_streamfile_filename(sf_mus, vgmstream->stream_name, STREAM_NAME_SIZE);
|
||||
close_streamfile(sf_mus);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(sf_mus);
|
||||
free_layout_segmented(data_s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//TODO remove a few lesser ones + use .txtm
|
||||
|
||||
/* open map/mpf+mus pairs that aren't exact pairs, since EA's games can load any combo */
|
||||
static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track /*, int num_tracks*/) {
|
||||
static const char *const mapfile_pairs[][2] = {
|
||||
/* standard cases, replace map part with mus part (from the end to preserve prefixes) */
|
||||
{"game.mpf", "Game_Stream.mus"}, /* Skate 1/2/3 */
|
||||
{"ipod.mpf", "Ipod_Stream.mus"},
|
||||
{"world.mpf", "World_Stream.mus"},
|
||||
{"FreSkate.mpf", "track.mus,ram.mus"}, /* Skate It */
|
||||
{"nsf_sing.mpf", "track_main.mus"}, /* Need for Speed: Nitro */
|
||||
{"nsf_wii.mpf", "Track.mus"},
|
||||
{"ssx_fe.mpf", "stream_1.mus,stream_2.mus"}, /* SSX 2012 */
|
||||
{"ssxdd.mpf", "main_trk.mus,"
|
||||
"trick_alaska0.mus,"
|
||||
"trick_rockies0.mus,"
|
||||
"trick_pata0.mus,"
|
||||
"trick_ant0.mus,"
|
||||
"trick_killi0.mus,"
|
||||
"trick_cyb0.mus,"
|
||||
"trick_hima0.mus,"
|
||||
"trick_nz0.mus,"
|
||||
"trick_alps0.mus,"
|
||||
"trick_lhotse0.mus"}
|
||||
};
|
||||
STREAMFILE *sf_mus = NULL;
|
||||
char file_name[PATH_LIMIT];
|
||||
int pair_count = (sizeof(mapfile_pairs) / sizeof(mapfile_pairs[0]));
|
||||
int i, j;
|
||||
size_t file_len, map_len;
|
||||
|
||||
/* try parsing TXTM if present */
|
||||
sf_mus = read_filemap_file(sf, track);
|
||||
if (sf_mus) return sf_mus;
|
||||
|
||||
/* if loading the first track, try opening MUS with the same name first (most common scenario) */
|
||||
if (track == 0) {
|
||||
sf_mus = open_streamfile_by_ext(sf, "mus");
|
||||
if (sf_mus) return sf_mus;
|
||||
}
|
||||
|
||||
get_streamfile_filename(sf, file_name, PATH_LIMIT);
|
||||
file_len = strlen(file_name);
|
||||
|
||||
for (i = 0; i < pair_count; i++) {
|
||||
const char *map_name = mapfile_pairs[i][0];
|
||||
const char *mus_name = mapfile_pairs[i][1];
|
||||
char buf[PATH_LIMIT] = { 0 };
|
||||
char *pch;
|
||||
int use_mask = 0;
|
||||
map_len = strlen(map_name);
|
||||
|
||||
/* replace map_name with expected mus_name */
|
||||
if (file_len < map_len)
|
||||
continue;
|
||||
|
||||
if (map_name[0] == '*') {
|
||||
use_mask = 1;
|
||||
map_name++;
|
||||
map_len--;
|
||||
|
||||
if (strncmp(file_name + (file_len - map_len), map_name, map_len) != 0)
|
||||
continue;
|
||||
} else {
|
||||
if (strcmp(file_name, map_name) != 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
strncpy(buf, mus_name, PATH_LIMIT - 1);
|
||||
pch = strtok(buf, ","); //TODO: not thread safe in std C
|
||||
for (j = 0; j < track && pch; j++) {
|
||||
pch = strtok(NULL, ",");
|
||||
}
|
||||
if (!pch) continue; /* invalid track */
|
||||
|
||||
if (use_mask) {
|
||||
file_name[file_len - map_len] = '\0';
|
||||
strncat(file_name, pch + 1, PATH_LIMIT - 1);
|
||||
} else {
|
||||
strncpy(file_name, pch, PATH_LIMIT - 1);
|
||||
}
|
||||
|
||||
sf_mus = open_streamfile_by_filename(sf, file_name);
|
||||
if (sf_mus) return sf_mus;
|
||||
|
||||
get_streamfile_filename(sf, file_name, PATH_LIMIT); /* reset for next loop */
|
||||
}
|
||||
|
||||
/* hack when when multiple maps point to the same mus, uses name before "+"
|
||||
* ex. ZZZTR00A.TRJ+ZTR00PGR.MAP or ZZZTR00A.TRJ+ZTR00R0A.MAP both point to ZZZTR00A.TRJ */
|
||||
{
|
||||
char *mod_name = strchr(file_name, '+');
|
||||
if (mod_name) {
|
||||
mod_name[0] = '\0';
|
||||
sf_mus = open_streamfile_by_filename(sf, file_name);
|
||||
if (sf_mus) return sf_mus;
|
||||
}
|
||||
}
|
||||
|
||||
vgm_logi("EA MPF: .mus file not found (find and put together)\n");
|
||||
return NULL;
|
||||
}
|
115
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_sbr.c
Normal file
115
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_sbr.c
Normal file
|
@ -0,0 +1,115 @@
|
|||
#include "meta.h"
|
||||
#include "../util/endianness.h"
|
||||
|
||||
#define EAAC_BLOCKID1_HEADER 0x48 /* 'H' */
|
||||
|
||||
|
||||
/* EA SBR/SBS - used in older 7th gen games for storing SFX */
|
||||
VGMSTREAM* init_vgmstream_ea_sbr(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE *sf_sbs = NULL;
|
||||
uint32_t num_sounds, sound_id, type_desc, num_items, item_type,
|
||||
table_offset, types_offset, entry_offset, items_offset, data_offset, snr_offset, sns_offset;
|
||||
int target_stream = sf->stream_index;
|
||||
eaac_meta_t info = {0};
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00, sf, "SBKR"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf, "sbr"))
|
||||
return NULL;
|
||||
|
||||
/* SBR files are always big endian */
|
||||
num_sounds = read_u32be(0x1c, sf);
|
||||
table_offset = read_u32be(0x24, sf);
|
||||
types_offset = read_u32be(0x28, sf);
|
||||
|
||||
if (target_stream == 0) target_stream = 1;
|
||||
if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
|
||||
goto fail;
|
||||
|
||||
entry_offset = table_offset + 0x0a * (target_stream - 1);
|
||||
sound_id = read_u32be(entry_offset + 0x00, sf);
|
||||
num_items = read_u16be(entry_offset + 0x04, sf);
|
||||
items_offset = read_u32be(entry_offset + 0x06, sf);
|
||||
|
||||
snr_offset = 0;
|
||||
sns_offset = 0;
|
||||
|
||||
for (uint32_t i = 0; i < num_items; i++) {
|
||||
entry_offset = items_offset + 0x06 * i;
|
||||
item_type = read_u16be(entry_offset + 0x00, sf);
|
||||
data_offset = read_u32be(entry_offset + 0x02, sf);
|
||||
|
||||
type_desc = read_u32be(types_offset + 0x06 * item_type, sf);
|
||||
|
||||
switch (type_desc) {
|
||||
case 0x534E5231: /* "SNR1" */
|
||||
snr_offset = data_offset;
|
||||
break;
|
||||
case 0x534E5331: /* "SNS1" */
|
||||
sns_offset = read_u32be(data_offset, sf);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (snr_offset == 0 && sns_offset == 0)
|
||||
goto fail;
|
||||
|
||||
if (sns_offset == 0) {
|
||||
/* RAM asset */
|
||||
|
||||
info.sf_head = sf;
|
||||
info.head_offset = snr_offset;
|
||||
info.body_offset = 0x00;
|
||||
info.type = (read_u8(snr_offset, sf) == EAAC_BLOCKID1_HEADER) ? meta_EA_SPS : meta_EA_SNR_SNS;;
|
||||
|
||||
vgmstream = load_vgmstream_ea_eaac(&info);
|
||||
if (!vgmstream) goto fail;
|
||||
}
|
||||
else {
|
||||
/* streamed asset */
|
||||
sf_sbs = open_streamfile_by_ext(sf, "sbs");
|
||||
if (!sf_sbs) goto fail;
|
||||
|
||||
if (!is_id32be(0x00, sf_sbs, "SBKS"))
|
||||
goto fail;
|
||||
|
||||
if (read_u8(sns_offset, sf_sbs) == EAAC_BLOCKID1_HEADER) {
|
||||
/* SPS */
|
||||
info.sf_head = sf_sbs;
|
||||
info.head_offset = sns_offset;
|
||||
info.body_offset = 0x00;
|
||||
info.type = meta_EA_SPS;
|
||||
|
||||
vgmstream = load_vgmstream_ea_eaac(&info);
|
||||
if (!vgmstream) goto fail;
|
||||
}
|
||||
else {
|
||||
/* SNR/SNS */
|
||||
if (snr_offset == 0)
|
||||
goto fail;
|
||||
|
||||
info.sf_head = sf;
|
||||
info.sf_body = sf_sbs;
|
||||
info.head_offset = snr_offset;
|
||||
info.body_offset = sns_offset;
|
||||
info.type = meta_EA_SNR_SNS;
|
||||
|
||||
vgmstream = load_vgmstream_ea_eaac(&info);
|
||||
if (!vgmstream) goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%08x", sound_id);
|
||||
vgmstream->num_streams = num_sounds;
|
||||
|
||||
close_streamfile(sf_sbs);
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_streamfile(sf_sbs);
|
||||
return NULL;
|
||||
}
|
243
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_sbr_harmony.c
Normal file
243
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_sbr_harmony.c
Normal file
|
@ -0,0 +1,243 @@
|
|||
#include "meta.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../util/endianness.h"
|
||||
|
||||
|
||||
/* EA Harmony Sample Bank - used in 8th gen EA Sports games */
|
||||
VGMSTREAM* init_vgmstream_ea_sbr_harmony(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE *sf_sbs = NULL, *sf_data = NULL;
|
||||
uint64_t base_offset, sound_offset, offset, prev_offset;
|
||||
uint32_t dset_id, dset_offset, num_values, num_fields, field_id,
|
||||
data_offset, table_offset, set_sounds, sound_table_offset;
|
||||
int16_t flag;
|
||||
uint16_t num_dsets;
|
||||
uint8_t set_type, offset_size;
|
||||
char sound_name[STREAM_NAME_SIZE];
|
||||
int target_stream = sf->stream_index, total_sounds, local_target, is_streamed = 0;
|
||||
int i, j;
|
||||
uint64_t(*read_u64)(off_t, STREAMFILE *);
|
||||
uint32_t(*read_u32)(off_t, STREAMFILE*);
|
||||
uint16_t(*read_u16)(off_t, STREAMFILE*);
|
||||
eaac_meta_t info = {0};
|
||||
|
||||
|
||||
/* checks */
|
||||
if (is_id32be(0x00, sf, "SBle")) {
|
||||
read_u64 = read_u64le;
|
||||
read_u32 = read_u32le;
|
||||
read_u16 = read_u16le;
|
||||
}
|
||||
else if (is_id32be(0x00, sf, "SBbe")) {
|
||||
read_u64 = read_u64be;
|
||||
read_u32 = read_u32be;
|
||||
read_u16 = read_u16be;
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!check_extensions(sf, "sbr"))
|
||||
return NULL;
|
||||
|
||||
num_dsets = read_u16(0x0a, sf);
|
||||
table_offset = read_u32(0x18, sf);
|
||||
data_offset = read_u32(0x20, sf);
|
||||
|
||||
if (target_stream == 0) target_stream = 1;
|
||||
if (target_stream < 0)
|
||||
goto fail;
|
||||
|
||||
total_sounds = 0;
|
||||
sound_offset = 0;
|
||||
|
||||
/* the bank is split into DSET sections each of which references one or multiple sounds */
|
||||
/* each set can contain RAM sounds (stored in SBR in data section) or streamed sounds (stored separately in SBS file) */
|
||||
for (i = 0; i < num_dsets; i++) {
|
||||
dset_offset = read_u32(table_offset + 0x08 * i, sf);
|
||||
if (read_u32(dset_offset, sf) != get_id32be("DSET"))
|
||||
goto fail;
|
||||
|
||||
dset_id = read_u32(dset_offset + 0x08, sf);
|
||||
num_values = read_u32(dset_offset + 0x38, sf);
|
||||
num_fields = read_u32(dset_offset + 0x3c, sf);
|
||||
local_target = target_stream - total_sounds - 1;
|
||||
dset_offset += 0x48;
|
||||
|
||||
/* find RAM or OFF field */
|
||||
for (j = 0; j < num_fields; j++) {
|
||||
field_id = read_u32(dset_offset, sf);
|
||||
if (field_id == get_id32be(".RAM") ||
|
||||
field_id == get_id32be(".OFF")) {
|
||||
break;
|
||||
}
|
||||
|
||||
dset_offset += 0x18;
|
||||
}
|
||||
|
||||
if (j == num_fields)
|
||||
goto fail;
|
||||
|
||||
/* different set types store offsets differently */
|
||||
set_type = read_u8(dset_offset + 0x05, sf);
|
||||
|
||||
/* data sets often contain duplicate offets, need to filter them out however we can */
|
||||
/* offsets are stored in ascending order which makes things easier */
|
||||
if (set_type == 0x00) {
|
||||
set_sounds = 1;
|
||||
total_sounds += set_sounds;
|
||||
if (local_target < 0 || local_target > 0)
|
||||
continue;
|
||||
|
||||
sound_offset = read_u64(dset_offset + 0x08, sf);
|
||||
}
|
||||
else if (set_type == 0x01) {
|
||||
flag = (int16_t)read_u16(dset_offset + 0x06, sf);
|
||||
base_offset = read_u64(dset_offset + 0x08, sf);
|
||||
|
||||
set_sounds = num_values;
|
||||
total_sounds += set_sounds;
|
||||
if (local_target < 0 || local_target >= set_sounds)
|
||||
continue;
|
||||
|
||||
sound_offset = base_offset + flag * local_target;
|
||||
}
|
||||
else if (set_type == 0x02) {
|
||||
flag = (read_u16(dset_offset + 0x06, sf) >> 0) & 0xFF;
|
||||
offset_size = (read_u16(dset_offset + 0x06, sf) >> 8) & 0xFF;
|
||||
base_offset = read_u64(dset_offset + 0x08, sf);
|
||||
sound_table_offset = read_u32(dset_offset + 0x10, sf);
|
||||
|
||||
set_sounds = 0;
|
||||
prev_offset = UINT64_MAX;
|
||||
for (j = 0; j < num_values; j++) {
|
||||
if (offset_size == 0x01) {
|
||||
offset = read_u8(sound_table_offset + 0x01 * j, sf);
|
||||
}
|
||||
else if (offset_size == 0x02) {
|
||||
offset = read_u16(sound_table_offset + 0x02 * j, sf);
|
||||
}
|
||||
else if (offset_size == 0x04) {
|
||||
offset = read_u32(sound_table_offset + 0x04 * j, sf);
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
offset <<= flag;
|
||||
offset += base_offset;
|
||||
|
||||
if (offset != prev_offset) {
|
||||
if (set_sounds == local_target)
|
||||
sound_offset = offset;
|
||||
set_sounds++;
|
||||
}
|
||||
prev_offset = offset;
|
||||
}
|
||||
|
||||
total_sounds += set_sounds;
|
||||
if (local_target < 0 || local_target >= set_sounds)
|
||||
continue;
|
||||
}
|
||||
else if (set_type == 0x03) {
|
||||
offset_size = (read_u16(dset_offset + 0x06, sf) >> 8) & 0xFF;
|
||||
set_sounds = read_u64(dset_offset + 0x08, sf);
|
||||
sound_table_offset = read_u32(dset_offset + 0x10, sf);
|
||||
|
||||
total_sounds += set_sounds;
|
||||
if (local_target < 0 || local_target >= set_sounds)
|
||||
continue;
|
||||
|
||||
if (offset_size == 0x01) {
|
||||
sound_offset = read_u8(sound_table_offset + 0x01 * local_target, sf);
|
||||
}
|
||||
else if (offset_size == 0x02) {
|
||||
sound_offset = read_u16(sound_table_offset + 0x02 * local_target, sf);
|
||||
}
|
||||
else if (offset_size == 0x04) {
|
||||
sound_offset = read_u32(sound_table_offset + 0x04 * local_target, sf);
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else if (set_type == 0x04) {
|
||||
sound_table_offset = read_u32(dset_offset + 0x10, sf);
|
||||
|
||||
set_sounds = 0;
|
||||
prev_offset = UINT64_MAX;
|
||||
for (j = 0; j < num_values; j++) {
|
||||
offset = read_u64(sound_table_offset + 0x08 * j, sf);
|
||||
|
||||
if (sound_offset != prev_offset) {
|
||||
if (set_sounds == local_target)
|
||||
sound_offset = offset;
|
||||
set_sounds++;
|
||||
}
|
||||
prev_offset = offset;
|
||||
}
|
||||
|
||||
total_sounds += set_sounds;
|
||||
if (local_target < 0 || local_target >= set_sounds)
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(sound_name, STREAM_NAME_SIZE, "DSET %08x/%04d", dset_id, local_target);
|
||||
|
||||
if (field_id == get_id32be(".RAM")) {
|
||||
is_streamed = 0;
|
||||
}
|
||||
else if (field_id == get_id32be(".OFF")) {
|
||||
is_streamed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (sound_offset == 0)
|
||||
goto fail;
|
||||
|
||||
if (!is_streamed) {
|
||||
/* RAM asset */
|
||||
if (!is_id32be(data_offset, sf, "data") &&
|
||||
!is_id32be(data_offset, sf, "DATA"))
|
||||
goto fail;
|
||||
|
||||
sf_data = sf;
|
||||
sound_offset += data_offset;
|
||||
}
|
||||
else {
|
||||
/* streamed asset */
|
||||
sf_sbs = open_streamfile_by_ext(sf, "sbs");
|
||||
if (!sf_sbs) goto fail;
|
||||
|
||||
if (!is_id32be(0x00, sf_sbs, "data") &&
|
||||
!is_id32be(0x00, sf_sbs, "DATA"))
|
||||
goto fail;
|
||||
|
||||
sf_data = sf_sbs;
|
||||
|
||||
if (is_id32be(sound_offset, sf_data, "slot")) {
|
||||
/* skip "slot" section */
|
||||
sound_offset += 0x30;
|
||||
}
|
||||
}
|
||||
|
||||
info.sf_head = sf_data;
|
||||
info.head_offset = sound_offset;
|
||||
info.body_offset = 0x00;
|
||||
info.type = meta_EA_SPS;
|
||||
|
||||
vgmstream = load_vgmstream_ea_eaac(&info);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->num_streams = total_sounds;
|
||||
strncpy(vgmstream->stream_name, sound_name, STREAM_NAME_SIZE);
|
||||
|
||||
close_streamfile(sf_sbs);
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_streamfile(sf_sbs);
|
||||
return NULL;
|
||||
}
|
82
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_standard.c
Normal file
82
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_standard.c
Normal file
|
@ -0,0 +1,82 @@
|
|||
#include "meta.h"
|
||||
#include "../util/endianness.h"
|
||||
|
||||
|
||||
#define EAAC_BLOCKID1_HEADER 0x48 /* 'H' */
|
||||
|
||||
|
||||
/* .SNR+SNS - from EA latest games (~2005-2010), v0 header */
|
||||
VGMSTREAM* init_vgmstream_ea_snr_sns(STREAMFILE* sf) {
|
||||
eaac_meta_t info = {0};
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf,"snr"))
|
||||
return NULL;
|
||||
|
||||
info.sf_head = sf;
|
||||
info.head_offset = 0x00;
|
||||
info.body_offset = 0x00;
|
||||
info.type = meta_EA_SNR_SNS;
|
||||
info.standalone = true;
|
||||
return load_vgmstream_ea_eaac(&info);
|
||||
}
|
||||
|
||||
/* .SPS - from EA latest games (~2010~present), v1 header */
|
||||
VGMSTREAM* init_vgmstream_ea_sps(STREAMFILE* sf) {
|
||||
eaac_meta_t info = {0};
|
||||
|
||||
/* checks */
|
||||
if (read_u8(0x00, sf) != EAAC_BLOCKID1_HEADER) /* validated later but fails faster */
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"sps"))
|
||||
return NULL;
|
||||
|
||||
info.sf_head = sf;
|
||||
info.head_offset = 0x00;
|
||||
info.type = meta_EA_SPS;
|
||||
info.standalone = true;
|
||||
return load_vgmstream_ea_eaac(&info);
|
||||
}
|
||||
|
||||
/* .SNU - from EA Redwood Shores/Visceral games (Dead Space, Dante's Inferno, The Godfather 2), v0 header */
|
||||
VGMSTREAM* init_vgmstream_ea_snu(STREAMFILE* sf) {
|
||||
eaac_meta_t info = {0};
|
||||
read_u32_t read_u32 = NULL;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf,"snu"))
|
||||
return NULL;
|
||||
|
||||
/* EA SNU header (BE/LE depending on platform) */
|
||||
/* 0x00(1): related to sample rate? (03=48000)
|
||||
* 0x01(1): flags/count? (when set has extra block data before start_offset)
|
||||
* 0x02(1): always 0?
|
||||
* 0x03(1): channels? (usually matches but rarely may be 0)
|
||||
* 0x04(4): some size, maybe >>2 ~= number of frames
|
||||
* 0x08(4): start offset
|
||||
* 0x0c(4): some sub-offset? (0x20, found when @0x01 is set) */
|
||||
|
||||
/* use start offset as endianness flag */
|
||||
read_u32 = guess_read_u32(0x08,sf);
|
||||
|
||||
uint32_t body_offset = read_u32(0x08,sf);
|
||||
uint8_t block_id = read_u8(body_offset, sf);
|
||||
|
||||
|
||||
if (block_id == EAAC_BLOCKID1_HEADER) {
|
||||
/* Dead Space 3 (PC) */
|
||||
info.sf_head = sf;
|
||||
info.head_offset = body_offset; /* header also at 0x10, but useless in SPS */
|
||||
info.type = meta_EA_SNU;
|
||||
info.is_sps = true;
|
||||
}
|
||||
else {
|
||||
info.sf_head = sf;
|
||||
info.sf_body = sf;
|
||||
info.head_offset = 0x10; /* SNR header */
|
||||
info.body_offset = body_offset; /* SNR body */
|
||||
info.type = meta_EA_SNU;
|
||||
}
|
||||
|
||||
return load_vgmstream_ea_eaac(&info);
|
||||
}
|
71
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_tmx.c
Normal file
71
Frameworks/vgmstream/vgmstream/src/meta/ea_eaac_tmx.c
Normal file
|
@ -0,0 +1,71 @@
|
|||
#include "meta.h"
|
||||
#include "../util/endianness.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* EA TMX - used for engine sounds in NFS games (2007-2011) */
|
||||
VGMSTREAM* init_vgmstream_ea_tmx(STREAMFILE* sf) {
|
||||
uint32_t num_sounds, sound_type, table_offset, data_offset, entry_offset, sound_offset;
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
int target_stream = sf->stream_index;
|
||||
uint32_t(*read_u32)(off_t, STREAMFILE *);
|
||||
|
||||
|
||||
/* checks */
|
||||
if (is_id32be(0x0c, sf, "0001")) {
|
||||
read_u32 = read_u32be;
|
||||
}
|
||||
else if (is_id32le(0x0c, sf, "1000")) {
|
||||
read_u32 = read_u32le;
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!check_extensions(sf, "tmx"))
|
||||
return NULL;
|
||||
|
||||
num_sounds = read_u32(0x20, sf);
|
||||
table_offset = read_u32(0x58, sf);
|
||||
data_offset = read_u32(0x5c, sf);
|
||||
|
||||
if (target_stream == 0) target_stream = 1;
|
||||
if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds)
|
||||
goto fail;
|
||||
|
||||
entry_offset = table_offset + (target_stream - 1) * 0x24;
|
||||
sound_type = read_u32(entry_offset + 0x00, sf);
|
||||
sound_offset = read_u32(entry_offset + 0x08, sf) + data_offset;
|
||||
|
||||
switch (sound_type) {
|
||||
case 0x47494E20: /* "GIN " */
|
||||
temp_sf = setup_subfile_streamfile(sf, sound_offset, get_streamfile_size(sf) - sound_offset, "gin");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_gin(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
close_streamfile(temp_sf);
|
||||
break;
|
||||
case 0x534E5220: { /* "SNR " */
|
||||
eaac_meta_t info = {0};
|
||||
|
||||
info.sf_head = sf;
|
||||
info.head_offset = sound_offset;
|
||||
info.body_offset = 0x00;
|
||||
info.type = meta_EA_SNR_SNS;
|
||||
|
||||
vgmstream = load_vgmstream_ea_eaac(&info);
|
||||
if (!vgmstream) goto fail;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->num_streams = num_sounds;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
return NULL;
|
||||
}
|
|
@ -297,8 +297,9 @@ VGMSTREAM* init_vgmstream_ea_bnk(STREAMFILE* sf) {
|
|||
* .sdt: Harry Potter games, Burnout games (PSP)
|
||||
* .hdt/ldt: Burnout games (PSP)
|
||||
* .abk: GoldenEye - Rogue Agent
|
||||
* .ast: FIFA 2004 (inside .big) */
|
||||
if (!check_extensions(sf,"bnk,sdt,hdt,ldt,abk,ast"))
|
||||
* .ast: FIFA 2004 (inside .big)
|
||||
* .cat: FIFA 2000 (PC, chant.cat) */
|
||||
if (!check_extensions(sf,"bnk,sdt,hdt,ldt,abk,ast,cat"))
|
||||
goto fail;
|
||||
|
||||
if (target_stream == 0) target_stream = 1;
|
||||
|
|
|
@ -84,7 +84,7 @@ VGMSTREAM* init_vgmstream_ego_dic(STREAMFILE* sf) {
|
|||
if (sb == NULL) {
|
||||
vgm_logi("DIC1: external file '%s' not found (put together)\n", resource_name);
|
||||
/* allow missing as silence since some game use huge .dic that is a bit hard to get */
|
||||
//goto fail;
|
||||
codec = 0xFFFFFFFF; //goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,6 +100,13 @@ VGMSTREAM* init_vgmstream_ego_dic(STREAMFILE* sf) {
|
|||
|
||||
|
||||
switch(codec) {
|
||||
case 0xFFFFFFFF: //fake
|
||||
vgmstream->coding_type = coding_SILENCE;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
vgmstream->num_samples = sample_rate;
|
||||
break;
|
||||
|
||||
case 0x57495000: //WIP\0
|
||||
vgmstream->coding_type = coding_PCM16LE;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
|
@ -143,10 +150,6 @@ VGMSTREAM* init_vgmstream_ego_dic(STREAMFILE* sf) {
|
|||
|
||||
vgmstream->loop_start_sample = 0;
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
if (!sb) {
|
||||
vgmstream->coding_type = coding_SILENCE;
|
||||
vgmstream->layout_type = layout_none;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sb, stream_offset))
|
||||
goto fail;
|
||||
|
|
|
@ -154,8 +154,8 @@ static VGMSTREAM* init_vgmstream_encrypted_rpgmvo_riff(STREAMFILE* sf) {
|
|||
e.key_size = 0x10;
|
||||
load_key(&cfg, e.keybuf, e.key_size);
|
||||
cfg.start = 0x10;
|
||||
cfg.max_offset = 0x10;
|
||||
|
||||
cfg.max_offset = 0x20;
|
||||
|
||||
e.temp_sf = setup_ogg_vorbis_streamfile(sf, &cfg);
|
||||
if (!e.temp_sf) goto fail;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "../util/chunks.h"
|
||||
|
||||
|
||||
static int get_subsongs(STREAMFILE* sf, off_t fsb5_offset, size_t fsb5_size);
|
||||
static int get_subsongs(STREAMFILE* sf, uint32_t fsb5_offset, uint32_t fsb5_size);
|
||||
|
||||
/* FEV+FSB5 container [Just Cause 3 (PC), Shantae: Half-Genie Hero (Switch)] */
|
||||
VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
|
||||
|
@ -88,7 +88,7 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
|
|||
|
||||
/* 0x00: unknown (chunk version? ex LE: 0x00080003, 0x00080005) */
|
||||
banks = (bank_size - 0x04) / entry_size;
|
||||
|
||||
|
||||
/* multiple banks is possible but rare [Hades (Switch), Guacamelee 2 (Switch)],
|
||||
* must map bank (global) subsong to FSB (internal) subsong */
|
||||
|
||||
|
@ -99,11 +99,11 @@ VGMSTREAM* init_vgmstream_fsb5_fev_bank(STREAMFILE* sf) {
|
|||
total_subsongs = 0;
|
||||
for (i = 0; i < banks; i++) {
|
||||
//TODO: fsb5_size fails for v0x28< + encrypted, but only used with multibanks = unlikely
|
||||
off_t fsb5_offset = read_u32le(bank_offset + 0x04 + entry_size*i + 0x00,sf);
|
||||
size_t fsb5_size = read_u32le(bank_offset+0x08 + entry_size*i,sf);
|
||||
uint32_t fsb5_offset = read_u32le(bank_offset + 0x04 + entry_size*i + 0x00,sf);
|
||||
uint32_t fsb5_size = read_u32le(bank_offset + 0x08 + entry_size*i,sf);
|
||||
int fsb5_subsongs = get_subsongs(sf, fsb5_offset, fsb5_size);
|
||||
if (!fsb5_subsongs) {
|
||||
vgm_logi("FSB: couldn't load bank (encrypted?)\n");
|
||||
vgm_logi("FSB: couldn't load bank %i at %x (encrypted?)\n", i, fsb5_offset);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -159,14 +159,13 @@ fail:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static int get_subsongs(STREAMFILE* sf, off_t fsb5_offset, size_t fsb5_size) {
|
||||
static int get_subsongs(STREAMFILE* sf, uint32_t fsb5_offset, uint32_t fsb5_size) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
int subsongs = 0;
|
||||
|
||||
|
||||
/* standard */
|
||||
if (read_u32be(fsb5_offset, sf) == 0x46534235) { /* FSB5 */
|
||||
if (is_id32be(fsb5_offset, sf, "FSB5")) {
|
||||
return read_s32le(fsb5_offset + 0x08,sf);
|
||||
}
|
||||
|
||||
|
@ -174,6 +173,7 @@ static int get_subsongs(STREAMFILE* sf, off_t fsb5_offset, size_t fsb5_size) {
|
|||
temp_sf = setup_subfile_streamfile(sf, fsb5_offset, fsb5_size, "fsb");
|
||||
if (!temp_sf) goto end;
|
||||
|
||||
temp_sf->stream_index = 0;
|
||||
vgmstream = init_vgmstream_fsb_encrypted(temp_sf);
|
||||
if (!vgmstream) goto end;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef _FSB_ENCRYPTED_STREAMFILE_H_
|
||||
#define _FSB_ENCRYPTED_STREAMFILE_H_
|
||||
|
||||
#define FSB_KEY_MAX 0x10000 //0x168
|
||||
#define FSB_KEY_MAX 0x80 /* known max ~0x33 */
|
||||
|
||||
|
||||
typedef struct {
|
||||
|
@ -57,7 +57,7 @@ static STREAMFILE* setup_fsb_streamfile(STREAMFILE* sf, const uint8_t* key, size
|
|||
size_t io_data_size = sizeof(fsb_decryption_data);
|
||||
|
||||
/* setup decryption with key (external) */
|
||||
if (!key_size || key_size > FSB_KEY_MAX)
|
||||
if (!key_size || key_size >= FSB_KEY_MAX)
|
||||
return NULL;
|
||||
|
||||
memcpy(io_data.key, key, key_size);
|
||||
|
|
|
@ -21,7 +21,7 @@ typedef struct {
|
|||
#define FLAG_FSB4 (1 << 0) /* key is valid for FSB4/3 */
|
||||
#define FLAG_FSB5 (1 << 1) /* key is valid for FSB5 */
|
||||
#define FLAG_STD (1 << 2) /* regular XOR mode */
|
||||
#define FLAG_ALT (1 << 3) /* alt XOR mode (seemingly not tied to FSB version or anything, maybe wrong key) */
|
||||
#define FLAG_ALT (1 << 3) /* alt XOR mode (seemingly older files or possibly FSB3 only) */
|
||||
|
||||
#define MODE_FSB4_STD (FLAG_FSB4 | FLAG_STD)
|
||||
#define MODE_FSB4_ALT (FLAG_FSB4 | FLAG_ALT)
|
||||
|
@ -55,10 +55,8 @@ static const fsbkey_info fsbkey_list[] = {
|
|||
{ MODE_FSBS_ALL, FSBKEY_ADD("5atu6w4zaw") }, // Guitar Hero 3 [untested]
|
||||
{ MODE_FSBS_ALL, FSBKEY_ADD("B2A7BB00") }, // Supreme Commander 2 [untested]
|
||||
{ MODE_FSB4_STD, FSBKEY_ADD("ghfxhslrghfxhslr") }, // Cookie Run: Ovenbreak
|
||||
{ MODE_FSB4_ALT, FSBKEY_ADD("truck/impact/carbody") },// Monster Jam (PS2) [FSB3]
|
||||
{ MODE_FSB4_ALT, FSBKEY_ADD("truck/impact/carbody") }, // Monster Jam (PS2) [FSB3]
|
||||
{ MODE_FSB4_ALT, FSBKEY_ADD("\xFC\xF9\xE4\xB3\xF5\x57\x5C\xA5\xAC\x13\xEC\x4A\x43\x19\x58\xEB\x4E\xF3\x84\x0B\x8B\x78\xFA\xFD\xBB\x18\x46\x7E\x31\xFB\xD0") }, // Guitar Hero 5 (X360)
|
||||
{ MODE_FSB4_ALT, FSBKEY_ADD("\x8C\xFA\xF3\x14\xB1\x53\xDA\xAB\x2B\x82\x6B\xD5\x55\x16\xCF\x01\x90\x20\x28\x14\xB1\x53\xD8") }, // Guitar Hero: Metallica (X360)
|
||||
{ MODE_FSB4_STD, FSBKEY_ADD("\xd2\x37\x70\x39\xa9\x86\xc5\xaf\x5b\x7f\xa2\x23\x98\x7e\xb6\xc2\x7e\x18\x7b\x2d\xd9\x31\x4b\x20\xb0\xc1\x8d\x06\xf2\xa7\xcd") }, // Guitar Hero: Metallica (PS3) [FSB4]
|
||||
{ MODE_FSB5_STD, FSBKEY_ADD("G0KTrWjS9syqF7vVD6RaVXlFD91gMgkC") }, // Sekiro: Shadows Die Twice (PC)
|
||||
{ MODE_FSB5_STD, FSBKEY_ADD("BasicEncryptionKey") }, // SCP: Unity (PC)
|
||||
{ MODE_FSB5_STD, FSBKEY_ADD("FXnTffGJ9LS855Gc") }, // Worms Rumble Beta (PC)
|
||||
|
@ -73,6 +71,16 @@ static const fsbkey_info fsbkey_list[] = {
|
|||
{ MODE_FSB5_STD, FSBKEY_ADD("Aurogon666") }, // Afterimage demo (PC)
|
||||
{ MODE_FSB5_STD, FSBKEY_ADD("IfYouLikeThosesSoundsWhyNotRenumerateTheir2Authors?") }, // Blanc (PC/Switch)
|
||||
{ MODE_FSB5_STD, FSBKEY_ADD("L36nshM520") }, // Nishuihan Mobile (Android)
|
||||
{ MODE_FSB5_STD, FSBKEY_ADD("Forza2!") }, // Forza Motorsport (PC)
|
||||
{ MODE_FSB5_STD, FSBKEY_ADD("cbfjZTlUPaZI") }, // JDM: Japanese Drift Master (PC)
|
||||
{ MODE_FSB4_ALT, FSBKEY_ADD("tkdnsem000") }, // Ys Online: The Call of Solum (PC) [FSB3] (alt key: 2ED62676CEA6B60C0C0C)
|
||||
{ MODE_FSB4_STD, FSBKEY_ADD("4DxgpNV3pQLPD6GT7g9Gf6eWU7SXutGQ") }, // Test Drive: Ferrari Racing Legends (PC)
|
||||
{ MODE_FSB4_STD, FSBKEY_ADD("AjaxIsTheGoodestBoy") }, // Hello Kitty: Island Adventure (iOS)
|
||||
|
||||
/* these games use a key per file, generated from the filename; could be possible to add them but there is a lot of songs,
|
||||
so external .fsbkey may be better (use guessfsb 3.1 with --write-key-file or ) */
|
||||
//{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero: Metallica (PC/PS3/X360) [FSB4]
|
||||
//{ MODE_FSB4_STD, FSBKEY_ADD("...") }, // Guitar Hero: World Tour (PC/PS3/X360) [FSB4]
|
||||
};
|
||||
static const int fsbkey_list_count = sizeof(fsbkey_list) / sizeof(fsbkey_list[0]);
|
||||
|
||||
|
|
57
Frameworks/vgmstream/vgmstream/src/meta/gbts.c
Normal file
57
Frameworks/vgmstream/vgmstream/src/meta/gbts.c
Normal file
|
@ -0,0 +1,57 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* GbTs - from Konami/KCE Studio games [Pop'n Music 9/10 (PS2)] */
|
||||
VGMSTREAM* init_vgmstream_gbts(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
uint32_t data_offset, data_size;
|
||||
int loop_flag, channels, sample_rate;
|
||||
uint32_t loop_start, loop_end;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "GbTs"))
|
||||
return NULL;
|
||||
/* .gbts: header id (no apparent exts) */
|
||||
if (!check_extensions(sf, "gbts"))
|
||||
return NULL;
|
||||
|
||||
|
||||
/* 04: always 0x24 */
|
||||
data_offset = read_u32le(0x08,sf);
|
||||
data_size = read_u32le(0x0C,sf); /* without padding */
|
||||
loop_start = read_u32le(0x10,sf); /* (0x20 = start frame if not set) */
|
||||
loop_end = read_u32le(0x14,sf); /* (0x00 if not set) */
|
||||
sample_rate = read_s32le(0x18,sf);
|
||||
channels = read_s32le(0x1C,sf);
|
||||
/* 20: 1? */
|
||||
/* 24: block size (interleave * channels) */
|
||||
/* 30+: empty */
|
||||
|
||||
loop_flag = (loop_end > 0);
|
||||
loop_end += loop_start; /* loop region matches PS-ADPCM flags */
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = ps_bytes_to_samples(data_size, channels);
|
||||
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channels);
|
||||
vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channels);
|
||||
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x10;
|
||||
|
||||
vgmstream->meta_type = meta_GBTS;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, data_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -1,96 +1,145 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../util/endianness.h"
|
||||
|
||||
typedef enum { XMA2, ATRAC9 } gtd_codec;
|
||||
//TODO rename gtd to ghs
|
||||
/* GHS - Hexadrive's HexaEngine games [Knights Contract (X360), Valhalla Knights 3 (Vita)] */
|
||||
typedef enum { PCM16LE, MSADPCM, XMA2, ATRAC9 } gtd_codec_t;
|
||||
|
||||
static void read_name(VGMSTREAM* vgmstream, STREAMFILE* sf, uint32_t offset);
|
||||
|
||||
|
||||
/* GHS - Hexadrive's HexaEngine games [Gunslinger Stratos (AC), Knights Contract (X360), Valhalla Knights 3 (Vita)] */
|
||||
VGMSTREAM* init_vgmstream_ghs(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset, chunk_offset, stpr_offset, name_offset = 0, loop_start_offset, loop_end_offset;
|
||||
size_t data_size, chunk_size;
|
||||
uint32_t stream_offset, stream_size, stpr_offset = 0, loop_start_offset = 0, loop_end_offset = 0;
|
||||
uint32_t chunk_offset, chunk_size = 0, at9_config_data = 0, block_size = 0;
|
||||
int loop_flag, channels, sample_rate;
|
||||
int num_samples, loop_start_sample, loop_end_sample;
|
||||
uint32_t at9_config_data;
|
||||
gtd_codec codec;
|
||||
int32_t num_samples, loop_start_sample, loop_end_sample;
|
||||
gtd_codec_t codec;
|
||||
int total_subsongs = 0, target_subsong = sf->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "GHS "))
|
||||
goto fail;
|
||||
if ( !check_extensions(sf,"gtd"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"gtd"))
|
||||
return NULL;
|
||||
|
||||
int big_endian = guess_endian32(0x04,sf);
|
||||
read_u32_t read_u32 = big_endian ? read_u32be : read_u32le;
|
||||
read_u16_t read_u16 = big_endian ? read_u16be : read_u16le;
|
||||
|
||||
int is_old = 0x34 + read_u32le(0x30,sf) + read_u32le(0x0c,sf) == get_streamfile_size(sf);
|
||||
|
||||
total_subsongs = read_u32(0x04, sf); /* seen in sfx packs inside .ged */
|
||||
if (!check_subsongs(&target_subsong, total_subsongs))
|
||||
return NULL;
|
||||
|
||||
/* not seen */
|
||||
if (target_subsong > 1 && is_old)
|
||||
goto fail;
|
||||
|
||||
|
||||
/* header type, not formally specified */
|
||||
if (read_32bitBE(0x04,sf) == 1 && read_16bitBE(0x0C,sf) == 0x0166) { /* XMA2 */
|
||||
/* 0x08(4): seek table size */
|
||||
chunk_offset = 0x0c; /* custom header with a "fmt " data chunk inside */
|
||||
chunk_size = 0x34;
|
||||
|
||||
channels = read_16bitBE(chunk_offset+0x02,sf);
|
||||
sample_rate = read_32bitBE(chunk_offset+0x04,sf);
|
||||
xma2_parse_fmt_chunk_extra(sf, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 1);
|
||||
|
||||
start_offset = read_32bitBE(0x58,sf); /* always 0x800 */
|
||||
data_size = read_32bitBE(0x5c,sf);
|
||||
/* 0x34(18): null, 0x54(4): seek table offset, 0x58(4): seek table size, 0x5c(8): null, 0x64: seek table */
|
||||
|
||||
stpr_offset = read_32bitBE(chunk_offset+0x54,sf) + read_32bitBE(chunk_offset+0x58,sf);
|
||||
if (is_id32be(stpr_offset,sf, "STPR")) {
|
||||
/* SRPR encases the original "S_P_STH" header (no data) */
|
||||
name_offset = stpr_offset + 0xB8; /* there are offsets fields but seems to work */
|
||||
/* header version, not formally specified */
|
||||
if (!is_old) {
|
||||
/* 0x08: size of all seek tables (XMA2, all tables go together after headers) / null */
|
||||
uint32_t offset = 0x0c + (target_subsong - 1) * 0x64;
|
||||
|
||||
int format = read_u16(offset + 0x00,sf);
|
||||
if (format == 0x0001)
|
||||
codec = PCM16LE; /* GS bgm */
|
||||
else if (format == 0x0002)
|
||||
codec = MSADPCM; /* GS sfx */
|
||||
else if (format == 0x0166) {
|
||||
codec = XMA2;
|
||||
chunk_offset = offset; /* "fmt " */
|
||||
chunk_size = 0x34;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec = XMA2;
|
||||
}
|
||||
else if (0x34 + read_32bitLE(0x30,sf) + read_32bitLE(0x0c,sf) == get_streamfile_size(sf)) { /* ATRAC9 */
|
||||
/* 0x0c: standard fmt chunk (depending on format, reserved with padding up to 0x48 if needed) */
|
||||
channels = read_u16(offset + 0x02,sf);
|
||||
sample_rate = read_u32(offset + 0x04,sf);
|
||||
block_size = read_u16(offset + 0x0c,sf);
|
||||
/* loops can be found at 0x28/2c in PCM16 (also later) */
|
||||
stream_offset = read_u32(offset + 0x4c,sf); /* always 0x800 */
|
||||
stream_size = read_u32(offset + 0x50,sf);
|
||||
/* 0x54: seek table offset (XMA2) / data start */
|
||||
/* 0x58: seek table size (XMA2) / null */
|
||||
loop_start_sample = read_u32(offset + 0x5c,sf); /* null in XMA2 */
|
||||
loop_end_sample = read_u32(offset + 0x60,sf) + loop_start_sample; /* +1? */
|
||||
|
||||
data_size = read_32bitLE(0x0c,sf);
|
||||
start_offset = 0x34 + read_32bitLE(0x30,sf);
|
||||
channels = read_32bitLE(0x10,sf);
|
||||
sample_rate = read_32bitLE(0x14,sf);
|
||||
loop_start_offset = read_32bitLE(0x1c, sf);
|
||||
loop_end_offset = read_32bitLE(0x20, sf);
|
||||
loop_flag = loop_end_offset > loop_start_offset;
|
||||
at9_config_data = read_32bitBE(0x28,sf);
|
||||
/* 0x18-0x28: fixed/unknown values */
|
||||
|
||||
stpr_offset = 0x2c;
|
||||
if (is_id32be(stpr_offset,sf, "STPR")) {
|
||||
/* STPR encases the original "S_P_STH" header (no data) */
|
||||
name_offset = stpr_offset + 0xE8; /* there are offsets fields but seems to work */
|
||||
if (codec == XMA2) {
|
||||
xma2_parse_fmt_chunk_extra(sf, chunk_offset, &loop_flag, &num_samples, &loop_start_sample, &loop_end_sample, 1);
|
||||
}
|
||||
else {
|
||||
loop_flag = loop_end_sample != 0;
|
||||
}
|
||||
|
||||
codec = ATRAC9;
|
||||
stpr_offset = read_u32(offset + 0x54,sf) + read_u32(offset + 0x58,sf);
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
codec = ATRAC9;
|
||||
|
||||
/* 08: always 02? */
|
||||
stream_size = read_u32(0x0c,sf);
|
||||
channels = read_u32(0x10,sf);
|
||||
sample_rate = read_u32(0x14,sf);
|
||||
/* 18: null? */
|
||||
loop_start_offset = read_u32(0x1c,sf);
|
||||
loop_end_offset = read_u32(0x20,sf);
|
||||
/* 24: channel layout? */
|
||||
at9_config_data = read_u32be(0x28,sf);
|
||||
/* 2c: STPR */
|
||||
stream_offset = read_u32(0x30,sf) + 0x34;
|
||||
loop_flag = loop_end_offset > loop_start_offset;
|
||||
|
||||
stpr_offset = 0x2c;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_GHS;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->loop_start_sample = loop_start_sample;
|
||||
vgmstream->loop_end_sample = loop_end_sample;
|
||||
vgmstream->meta_type = meta_GHS;
|
||||
if (name_offset) //encoding is Shift-Jis in some PSV files
|
||||
read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,sf);
|
||||
vgmstream->loop_end_sample = loop_end_sample;
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
read_name(vgmstream, sf, stpr_offset);
|
||||
|
||||
switch(codec) {
|
||||
case PCM16LE:
|
||||
vgmstream->coding_type = coding_PCM16LE;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = block_size / channels;
|
||||
|
||||
vgmstream->num_samples = pcm16_bytes_to_samples(stream_size, channels);
|
||||
|
||||
break;
|
||||
|
||||
case MSADPCM:
|
||||
vgmstream->coding_type = coding_MSADPCM;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->frame_size = block_size;
|
||||
|
||||
vgmstream->num_samples = msadpcm_bytes_to_samples(stream_size, block_size, channels);
|
||||
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case XMA2:
|
||||
vgmstream->codec_data = init_ffmpeg_xma_chunk(sf, start_offset, data_size, chunk_offset, chunk_size);
|
||||
vgmstream->codec_data = init_ffmpeg_xma_chunk(sf, stream_offset, stream_size, chunk_offset, chunk_size);
|
||||
if ( !vgmstream->codec_data ) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
vgmstream->num_samples = num_samples;
|
||||
|
||||
xma_fix_raw_samples(vgmstream, sf, start_offset, data_size, chunk_offset, 1,1);
|
||||
xma_fix_raw_samples(vgmstream, sf, stream_offset, stream_size, chunk_offset, 1,1);
|
||||
break;
|
||||
#endif
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
|
@ -105,13 +154,12 @@ VGMSTREAM* init_vgmstream_ghs(STREAMFILE* sf) {
|
|||
vgmstream->coding_type = coding_ATRAC9;
|
||||
vgmstream->layout_type = layout_none;
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = atrac9_bytes_to_samples(loop_start_offset - start_offset, vgmstream->codec_data);
|
||||
vgmstream->loop_end_sample = atrac9_bytes_to_samples(loop_end_offset - start_offset, vgmstream->codec_data);
|
||||
vgmstream->loop_start_sample = atrac9_bytes_to_samples(loop_start_offset - stream_offset, vgmstream->codec_data);
|
||||
vgmstream->loop_end_sample = atrac9_bytes_to_samples(loop_end_offset - stream_offset, vgmstream->codec_data);
|
||||
}
|
||||
vgmstream->num_samples = atrac9_bytes_to_samples(data_size, vgmstream->codec_data);
|
||||
vgmstream->num_samples = atrac9_bytes_to_samples(stream_size, vgmstream->codec_data);
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
default:
|
||||
|
@ -119,7 +167,7 @@ VGMSTREAM* init_vgmstream_ghs(STREAMFILE* sf) {
|
|||
}
|
||||
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
if (!vgmstream_open_stream(vgmstream, sf, stream_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
|
@ -131,14 +179,14 @@ fail:
|
|||
VGMSTREAM* init_vgmstream_s_p_sth(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
uint32_t subfile_offset, subfile_size, name_offset;
|
||||
uint32_t subfile_offset, subfile_size, stpr_offset;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id64be(0x00,sf,"S_P_STH\x01"))
|
||||
goto fail;
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"gtd"))
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
subfile_offset = read_u32be(0x08, sf);
|
||||
subfile_size = get_streamfile_size(sf) - subfile_offset;
|
||||
|
@ -150,8 +198,52 @@ VGMSTREAM* init_vgmstream_s_p_sth(STREAMFILE* sf) {
|
|||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_GHS;
|
||||
name_offset = 0xB0; /* there are offsets fields but seems to work */
|
||||
read_string(vgmstream->stream_name, STREAM_NAME_SIZE, name_offset, sf);
|
||||
|
||||
stpr_offset = 0x00;
|
||||
read_name(vgmstream, sf, stpr_offset);
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* S_PACK - Hexadrive's HexaEngine games [Gunslinger Stratos (PC), Knights Contract (X360)] */
|
||||
VGMSTREAM* init_vgmstream_s_pack(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
|
||||
/* checks */
|
||||
if (!is_id64be(0x00,sf,"S_PACK\x00\x00") && !is_id64be(0x00,sf,"S_PACK\x00\x01")) /* v1: KC */
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"ged"))
|
||||
return NULL;
|
||||
/* 0x08: file size */
|
||||
/* 0x0c-0x20: null */
|
||||
|
||||
int big_endian = guess_endian32(0x20,sf);
|
||||
read_u32_t read_u32 = big_endian ? read_u32be : read_u32le;
|
||||
|
||||
uint32_t offset = read_u32(0x20, sf); /* offset to minitable */
|
||||
/* 0x24: number of chunks in S_P_H? */
|
||||
/* 0x28: number of entries in minitable */
|
||||
|
||||
/* minitable */
|
||||
/* 0x00: offset to "S_P_H", that seems to have cuenames (may have more cues than waves though) */
|
||||
uint32_t subfile_offset = read_u32(offset + 0x04, sf); /* may be null or S_CHR_M (no GHS in file) */
|
||||
uint32_t schar_offset = read_u32(offset + 0x08, sf); /* S_CHR_M seen in KC, some kind of cues */
|
||||
|
||||
if (schar_offset == 0)
|
||||
schar_offset = get_streamfile_size(sf);
|
||||
uint32_t subfile_size = schar_offset - subfile_offset;
|
||||
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, "gtd");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_ghs(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
|
@ -161,3 +253,41 @@ fail:
|
|||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void read_name(VGMSTREAM* vgmstream, STREAMFILE* sf, uint32_t offset) {
|
||||
uint32_t name_offset = 0;
|
||||
|
||||
//if (!offset) //may be 0 in PS3
|
||||
// return;
|
||||
|
||||
if (is_id32be(offset,sf, "STPR"))
|
||||
offset += 0x08;
|
||||
|
||||
if (is_id64be(offset + 0x00,sf, "S_P_STH\0")) { /* stream header v0: GS, VK3 */
|
||||
/* 08 subheader size */
|
||||
/* 0c version/count? */
|
||||
/* 10 version/count? */
|
||||
/* 20 offset to header configs */
|
||||
/* 24 hash? */
|
||||
/* 2c -1? */
|
||||
/* 30 1? */
|
||||
if (!is_id64be(offset + 0x40,sf, "stream\0\0"))
|
||||
return;
|
||||
/* 50 bank name */
|
||||
/* 70+ header configs (some are repeats from GHS) */
|
||||
/* E0 file name .gtd */
|
||||
|
||||
name_offset = offset + 0xE0; /* show file name though actual files are already (bankname)_(filename).gtd */
|
||||
}
|
||||
else if (is_id64be(offset + 0x00,sf, "S_P_STH\1")) { /* stream header v1: KC */
|
||||
/* same as above, except no stream+bank name, so at 0x40 are header configs */
|
||||
name_offset = offset + 0xB0;
|
||||
}
|
||||
|
||||
/* optional (only found in streams, sfx packs that point to GHS have cue names) */
|
||||
if (!name_offset)
|
||||
return;
|
||||
|
||||
//TO-DO: Shift-Jis in some Vita files
|
||||
read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,sf);
|
||||
}
|
||||
|
|
149
Frameworks/vgmstream/vgmstream/src/meta/gwb_gwd.c
Normal file
149
Frameworks/vgmstream/vgmstream/src/meta/gwb_gwd.c
Normal file
|
@ -0,0 +1,149 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* GWB+GWD - Ubisoft bank [Monster 4x4: World Circuit (Wii)] */
|
||||
VGMSTREAM* init_vgmstream_gwb_gwd(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* sf_body = NULL;
|
||||
uint32_t stream_offset = 0, stream_size = 0, coef_offset;
|
||||
int loop_flag, channels, sample_rate, interleave = 0;
|
||||
uint32_t loop_start, loop_end;
|
||||
int total_subsongs, target_subsong = sf->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
int version = read_u8(0x00, sf);
|
||||
if (version != 6 && version != 7)
|
||||
return NULL;
|
||||
if (read_u32be(0x01, sf) > 0x0400) /* ID, max seen */
|
||||
return NULL;
|
||||
if (get_streamfile_size(sf) > 0x2000) /* arbitrary max */
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"gwb"))
|
||||
return NULL;
|
||||
|
||||
/* format (vaguely similar to ubi's hx and such banks)
|
||||
* common
|
||||
* 00: version (06/07, both found in the same game)
|
||||
* 01: file ID (low number: 0x0001, 0x0342...)
|
||||
* v6:
|
||||
* 05: subsongs
|
||||
* v7
|
||||
* 05: null
|
||||
* 09: subsongs
|
||||
*
|
||||
* per subsong:
|
||||
* - 00: flags: (v6: 09=stereo, 02=mono; v7: 0a=stereo)
|
||||
* - 01: id (ex. 0x0002, 0x0343...)
|
||||
* v6
|
||||
* - 05: 0x4a header * channels
|
||||
* v7
|
||||
* - 05: always 0x02?
|
||||
* - 09: stream offset
|
||||
* - 0d: stream size
|
||||
* - 11: always 5
|
||||
* - 15: 0x4a header * channels
|
||||
*
|
||||
* per header:
|
||||
* - 00: loop flag
|
||||
* - 04: sample rate
|
||||
* - 08: loop start nibbles
|
||||
* - 0c: loop end nibbles
|
||||
* - 10: end nibble
|
||||
* - 14: start nibble (after DSP frame header, so uses 0x02 at file start)
|
||||
* - 18: null
|
||||
* - 1c: coefs + gain + initial ps/hists + loop ps/hists
|
||||
* Data in .gwd is N headerless DSPs. All nibble values are absolute within the file. */
|
||||
|
||||
uint32_t offset = version == 6 ? 0x05 : 0x09;
|
||||
|
||||
total_subsongs = read_s32be(offset, sf);
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) return NULL;
|
||||
offset += 0x04;
|
||||
|
||||
/* find target header */
|
||||
for (int i = 0; i < total_subsongs; i++) {
|
||||
if (i + 1 == target_subsong)
|
||||
break;
|
||||
|
||||
uint8_t type = read_u8(offset + 0x00, sf);
|
||||
if (type != 0x0a && type != 0x09 && type != 0x02)
|
||||
goto fail;
|
||||
|
||||
offset += 0x05 + (version == 7 ? 0x10 : 0);
|
||||
offset += 0x4a * (type & 0x08 ? 2 : 1);
|
||||
}
|
||||
|
||||
/* header */
|
||||
{
|
||||
uint32_t st_nibble, ed_nibble, ls_nibble, le_nibble;
|
||||
uint8_t type = read_u8(offset + 0x00, sf);
|
||||
channels = (type & 0x08 ? 2 : 1);
|
||||
|
||||
offset += 0x05;
|
||||
if (version == 7) {
|
||||
stream_offset = read_u32be(offset + 0x04, sf);
|
||||
stream_size = read_u32be(offset + 0x08, sf);
|
||||
interleave = 0x4000;
|
||||
offset += 0x10;
|
||||
}
|
||||
loop_flag = read_u32be(offset + 0x00, sf) == 1;
|
||||
sample_rate = read_u32be(offset + 0x04, sf);
|
||||
ls_nibble = read_u32be(offset + 0x08, sf);
|
||||
le_nibble = read_u32be(offset + 0x0c, sf);
|
||||
ed_nibble = read_u32be(offset + 0x10, sf);
|
||||
st_nibble = read_u32be(offset + 0x14, sf);
|
||||
coef_offset = offset + 0x1c;
|
||||
|
||||
if (version == 6) {
|
||||
stream_offset = ((st_nibble - 2) / 2);
|
||||
stream_size = ((ed_nibble - st_nibble - 2) / 2) * channels;
|
||||
|
||||
/* stereo repeats loop flag/sample rate/offsets/etc but simplify */
|
||||
if (channels == 2) {
|
||||
uint32_t s2_nibble = read_u32be(offset + 0x4a + 0x14, sf);
|
||||
interleave = (s2_nibble - st_nibble) / 2;
|
||||
}
|
||||
}
|
||||
loop_start = ((ls_nibble - 2) / 2 - stream_offset);
|
||||
loop_end = ((le_nibble) / 2 - stream_offset) * channels;
|
||||
}
|
||||
|
||||
/* files also have an optional companion .gsb with volume/etc config that seems to be adapted from Xbox's .xsb */
|
||||
sf_body = open_streamfile_by_ext(sf, "gwd");
|
||||
if (!sf_body) goto fail;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_GWB_GWD;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = dsp_bytes_to_samples(stream_size, channels);
|
||||
vgmstream->loop_start_sample = dsp_bytes_to_samples(loop_start, channels);
|
||||
vgmstream->loop_end_sample = dsp_bytes_to_samples(loop_end, channels);;
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
|
||||
dsp_read_coefs_be(vgmstream, sf, coef_offset + 0x00, 0x4a);
|
||||
dsp_read_hist_be (vgmstream, sf, coef_offset + 0x24, 0x4a);
|
||||
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf_body, stream_offset))
|
||||
goto fail;
|
||||
close_streamfile(sf_body);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(sf_body);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -1254,6 +1254,30 @@ static const hcakey_info hcakey_list[] = {
|
|||
// BlazBlue Entropy Effect (Early Access) (PC)
|
||||
{29814655674508831}, // 0069EC457894661F
|
||||
|
||||
// Star Ocean: The Second Story R (PC, Switch)
|
||||
{533948357975462459}, // 0768F733DD87D23B
|
||||
|
||||
// Girls' Frontline 2: Exilium (Android)
|
||||
{8930254087621254}, // 001FBA04CEA58A86
|
||||
|
||||
// Sonic Superstars (Switch)
|
||||
{1991062320230623}, // 000712DC5252B0DF
|
||||
|
||||
// Persona 5 Tactica (Switch)
|
||||
{48319776512953016}, // 00ABAA94AAAE4AB8
|
||||
|
||||
// THE IDOLM@STER Shiny Colors Song For Prism (PC)
|
||||
{156967709847897761}, // 022DA94CEAB0C6A1
|
||||
|
||||
// Dokapon Kingdom Connect (PC, Switch)
|
||||
{104863924750642073}, // 01748d2f1883eb99
|
||||
|
||||
// Girls' Frontline 2: Exilium (PC)
|
||||
{7152097263845921}, // 001968CB68CF8221
|
||||
|
||||
// Girls' Frontline 2: Exilium (PC)
|
||||
{4079616028775461768}, // 389DB529D726B388
|
||||
|
||||
};
|
||||
|
||||
#endif/*_HCA_KEYS_H_*/
|
||||
|
|
|
@ -389,7 +389,7 @@ VGMSTREAM* init_vgmstream_bsnf(STREAMFILE* sf) {
|
|||
if (stream_size != get_streamfile_size(sb))
|
||||
goto fail;
|
||||
|
||||
loop_flag = (loop_start > 0);
|
||||
loop_flag = (loop_start > 0); /* loops from 0 on some codecs aren't detectable though */
|
||||
start_offset = 0x00;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
|
@ -404,13 +404,18 @@ VGMSTREAM* init_vgmstream_bsnf(STREAMFILE* sf) {
|
|||
vgmstream->num_streams = num_languages;
|
||||
strncpy(vgmstream->stream_name, language, STREAM_NAME_SIZE);
|
||||
|
||||
/* for codecs with explicit encoder delay (mp3/at9/etc) num_samples includes it
|
||||
* ex. mus_c05_dream_doorloop_* does full loops; with some codecs loop start is encoder delay and num_samples
|
||||
* has extra delay samples compared to codecs with implicit delay (ex. mp3 1152 to 101152 vs ogg 0 to 100000),
|
||||
* but there is no header value for encoder delay, maybe engine hardcodes it? */
|
||||
|
||||
switch (codec) {
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
case 0x0055: {
|
||||
mpeg_custom_config cfg = { 0 };
|
||||
|
||||
cfg.skip_samples = 1152; /* seems ok */
|
||||
//cfg.skip_samples = 1152; /* observed default */
|
||||
|
||||
vgmstream->codec_data = init_mpeg_custom(sb, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_STANDARD, &cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
|
@ -451,14 +456,20 @@ VGMSTREAM* init_vgmstream_bsnf(STREAMFILE* sf) {
|
|||
case 0x42D2: {
|
||||
atrac9_config cfg = { 0 };
|
||||
|
||||
/* extra offset: RIFF fmt extra data (extra size, frame size, GUID, etc), no fact samples/delay */
|
||||
|
||||
cfg.channels = vgmstream->channels;
|
||||
cfg.encoder_delay = read_u16le(extra_offset + 0x02, sf);
|
||||
//cfg.encoder_delay = read_u16le(extra_offset + 0x02, sf) / 4; /* seemingly one subframe = 256 */
|
||||
cfg.config_data = read_u32be(extra_offset + 0x1c, sf);
|
||||
|
||||
vgmstream->codec_data = init_atrac9(&cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_ATRAC9;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
vgmstream->num_samples -= cfg.encoder_delay;
|
||||
vgmstream->loop_start_sample -= cfg.encoder_delay;
|
||||
vgmstream->loop_end_sample -= cfg.encoder_delay;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
#include "meta.h"
|
||||
#include "../util.h"
|
||||
|
||||
/* PSND (from Crash Bandicoot Nitro Kart 2 (iOS) */
|
||||
VGMSTREAM * init_vgmstream_ios_psnd(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
char filename[PATH_LIMIT];
|
||||
off_t start_offset;
|
||||
|
||||
int loop_flag;
|
||||
int channel_count;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
if (strcasecmp("psnd",filename_extension(filename))) goto fail;
|
||||
|
||||
/* check header */
|
||||
if (read_32bitBE(0x00,streamFile) != 0x50534E44) /* "PSND" */
|
||||
goto fail;
|
||||
|
||||
if (read_16bitBE(0xC,streamFile)==0x2256){
|
||||
loop_flag = 1;
|
||||
}
|
||||
else {
|
||||
loop_flag = 0;
|
||||
}
|
||||
channel_count = read_8bit(0xE,streamFile);
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* fill in the vital statistics */
|
||||
start_offset = 0x10;
|
||||
vgmstream->channels = channel_count;
|
||||
|
||||
if (read_16bitBE(0xC,streamFile)==0x44AC){
|
||||
vgmstream->sample_rate = 44100;
|
||||
}
|
||||
else {
|
||||
vgmstream->sample_rate = read_16bitLE(0xC,streamFile);
|
||||
}
|
||||
|
||||
vgmstream->coding_type = coding_PCM16LE;
|
||||
vgmstream->num_samples = (read_32bitLE(0x4,streamFile)-8)/4;
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = 0;
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
}
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 2;
|
||||
vgmstream->meta_type = meta_IOS_PSND;
|
||||
|
||||
/* open the file for reading */
|
||||
{
|
||||
int i;
|
||||
STREAMFILE * file;
|
||||
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (!file) goto fail;
|
||||
for (i=0;i<channel_count;i++) {
|
||||
vgmstream->ch[i].streamfile = file;
|
||||
|
||||
vgmstream->ch[i].channel_start_offset=
|
||||
vgmstream->ch[i].offset=start_offset+
|
||||
vgmstream->interleave_block_size*i;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -14,8 +14,7 @@ VGMSTREAM* init_vgmstream_ktsc(STREAMFILE* sf) {
|
|||
/* checks */
|
||||
if (!is_id32be(0x00, sf, "KTSC"))
|
||||
goto fail;
|
||||
if (read_u32be(0x04, sf) != 0x01000001) /* version? */
|
||||
goto fail;
|
||||
/* 0x04: version? (0x01000001: common, 0x01000004: FE Three Houses) */
|
||||
|
||||
/* .ktsl2asbin: common [Atelier Ryza (PC)]
|
||||
* .asbin: Warriors Orochi 4 (PC) (assumed) */
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "../util/companion_files.h"
|
||||
#include "ktsr_streamfile.h"
|
||||
|
||||
typedef enum { NONE, MSADPCM, DSP, GCADPCM, ATRAC9, RIFF_ATRAC9, KOVS, KTSS, } ktsr_codec;
|
||||
|
||||
#define MAX_CHANNELS 8
|
||||
|
||||
typedef struct {
|
||||
uint32_t base_offset;
|
||||
bool is_srsa;
|
||||
int total_subsongs;
|
||||
int target_subsong;
|
||||
ktsr_codec codec;
|
||||
|
||||
uint32_t audio_id;
|
||||
int platform;
|
||||
int format;
|
||||
uint32_t sound_id;
|
||||
uint32_t sound_flags;
|
||||
uint32_t config_flags;
|
||||
|
||||
int channels;
|
||||
int sample_rate;
|
||||
|
@ -31,32 +39,73 @@ typedef struct {
|
|||
char name[255+1];
|
||||
} ktsr_header;
|
||||
|
||||
static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa);
|
||||
static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf);
|
||||
static layered_layout_data* build_layered_atrac9(ktsr_header* ktsr, STREAMFILE *sf, uint32_t config_data);
|
||||
static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, ktsr_header* ktsr, VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf), const char* ext);
|
||||
|
||||
/* KTSR - Koei Tecmo sound resource container */
|
||||
VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00, sf, "KTSR"))
|
||||
return NULL;
|
||||
/* others: see internal */
|
||||
|
||||
/* .ktsl2asbin: common [Atelier Ryza (PC/Switch), Nioh (PC)]
|
||||
* .asbin: Warriors Orochi 4 (PC) */
|
||||
if (!check_extensions(sf, "ktsl2asbin,asbin"))
|
||||
return NULL;
|
||||
|
||||
return init_vgmstream_ktsr_internal(sf, false) ;
|
||||
}
|
||||
|
||||
/* ASRS - container of KTSR found in newer games */
|
||||
VGMSTREAM* init_vgmstream_asrs(STREAMFILE* sf) {
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00, sf, "ASRS"))
|
||||
return NULL;
|
||||
/* 0x04: null */
|
||||
/* 0x08: file size */
|
||||
/* 0x0c: null */
|
||||
|
||||
/* .srsa: header id (as generated by common tools, probably "(something)asbin") */
|
||||
if (!check_extensions(sf, "srsa"))
|
||||
return NULL;
|
||||
|
||||
/* mini-container of memory-KTSR (streams also have a "TSRS" for stream-KTSR).
|
||||
* .srsa/srst usually have hashed filenames, so it isn't easy to match them, so this
|
||||
* is mainly useful for .srsa with internal streams. */
|
||||
|
||||
return init_vgmstream_ktsr_internal(sf, true);
|
||||
}
|
||||
|
||||
|
||||
static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, bool is_srsa) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* sf_b = NULL;
|
||||
ktsr_header ktsr = {0};
|
||||
int target_subsong = sf->stream_index;
|
||||
int separate_offsets = 0;
|
||||
|
||||
ktsr.is_srsa = is_srsa;
|
||||
if (ktsr.is_srsa) {
|
||||
ktsr.base_offset = 0x10;
|
||||
}
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00, sf, "KTSR"))
|
||||
goto fail;
|
||||
if (read_u32be(0x04, sf) != 0x777B481A) /* hash(?) id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */
|
||||
goto fail;
|
||||
/* .ktsl2asbin: common [Atelier Ryza (PC/Switch), Nioh (PC)]
|
||||
* .asbin: Warriors Orochi 4 (PC) */
|
||||
if (!check_extensions(sf, "ktsl2asbin,asbin"))
|
||||
goto fail;
|
||||
if (!is_id32be(ktsr.base_offset + 0x00, sf, "KTSR"))
|
||||
return NULL;
|
||||
if (read_u32be(ktsr.base_offset + 0x04, sf) != 0x777B481A) /* hash id: 0x777B481A=as, 0x0294DDFC=st, 0xC638E69E=gc */
|
||||
return NULL;
|
||||
|
||||
|
||||
/* KTSR can be a memory file (ktsl2asbin), streams (ktsl2stbin) and global config (ktsl2gcbin)
|
||||
* This accepts .ktsl2asbin with internal data or external streams as subsongs.
|
||||
* Some info from KTSR.bt */
|
||||
* Hashes are meant to be read LE, but here are BE for easier comparison (they probably correspond
|
||||
* to some text but are pre-hashed in exes). Some info from KTSR.bt */
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
ktsr.target_subsong = target_subsong;
|
||||
|
@ -66,11 +115,22 @@ VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
|
|||
|
||||
/* open companion body */
|
||||
if (ktsr.is_external) {
|
||||
const char* companion_ext = check_extensions(sf, "asbin") ? "stbin" : "ktsl2stbin";
|
||||
sf_b = open_streamfile_by_ext(sf, companion_ext);
|
||||
if (ktsr.is_srsa) {
|
||||
/* try parsing TXTM if present, since .srsa+srst have hashed names and don't match unless renamed */
|
||||
sf_b = read_filemap_file(sf, 0);
|
||||
}
|
||||
|
||||
if (!sf_b) {
|
||||
vgm_logi("KTSR: companion file '*.%s' not found\n", companion_ext);
|
||||
goto fail;
|
||||
/* try (name).(ext), as seen in older games */
|
||||
const char* companion_ext = check_extensions(sf, "asbin") ? "stbin" : "ktsl2stbin";
|
||||
if (ktsr.is_srsa)
|
||||
companion_ext = "srst";
|
||||
|
||||
sf_b = open_streamfile_by_ext(sf, companion_ext);
|
||||
if (!sf_b) {
|
||||
vgm_logi("KTSR: companion file '*.%s' not found\n", companion_ext);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -172,15 +232,20 @@ fail:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
// TODO improve, unity with other metas that do similar stuff
|
||||
// TODO improve, unify with other metas that do similar stuff
|
||||
static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, ktsr_header* ktsr, VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf), const char* ext) {
|
||||
VGMSTREAM* sub_vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = setup_subfile_streamfile(sf_b, ktsr->stream_offsets[0], ktsr->stream_sizes[0], ext);
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
|
||||
temp_sf = setup_ktsr_streamfile(sf_b, ktsr->is_external, ktsr->stream_offsets[0], ktsr->stream_sizes[0], ext);
|
||||
if (!temp_sf) return NULL;
|
||||
|
||||
sub_vgmstream = init_vgmstream(temp_sf);
|
||||
close_streamfile(temp_sf);
|
||||
if (!sub_vgmstream) return NULL;
|
||||
if (!sub_vgmstream) {
|
||||
VGM_LOG("ktsr: can't open subfile at %x (size %x)\n", ktsr->stream_offsets[0], ktsr->stream_sizes[0]);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sub_vgmstream->stream_size = ktsr->stream_sizes[0];
|
||||
sub_vgmstream->num_streams = ktsr->total_subsongs;
|
||||
|
@ -253,6 +318,7 @@ static int parse_codec(ktsr_header* ktsr) {
|
|||
/* platform + format to codec, simplified until more codec combos are found */
|
||||
switch(ktsr->platform) {
|
||||
case 0x01: /* PC */
|
||||
case 0x05: /* PC/Steam [Fate/Samurai Remnant (PC)] */
|
||||
if (ktsr->is_external) {
|
||||
if (ktsr->format == 0x0005)
|
||||
ktsr->codec = KOVS; // Atelier Ryza (PC)
|
||||
|
@ -284,11 +350,13 @@ static int parse_codec(ktsr_header* ktsr) {
|
|||
if (ktsr->is_external) {
|
||||
if (ktsr->format == 0x0005)
|
||||
ktsr->codec = KTSS; // [Ultra Kaiju Monster Rancher (Switch)]
|
||||
else if (ktsr->format == 0x1000)
|
||||
ktsr->codec = KTSS; // [Fire Emblem: Three Houses (Switch)-some DSP voices]
|
||||
else
|
||||
goto fail;
|
||||
}
|
||||
else if (ktsr->format == 0x0000)
|
||||
ktsr->codec = DSP; // Fire Emblem: Three Houses (Switch)
|
||||
ktsr->codec = DSP; // [Fire Emblem: Three Houses (Switch)]
|
||||
else
|
||||
goto fail;
|
||||
break;
|
||||
|
@ -318,9 +386,11 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, uint32_t offset
|
|||
case 0x3DEA478D: /* external [Nioh (PC)] (smaller) */
|
||||
case 0xDF92529F: /* external [Atelier Ryza (PC)] */
|
||||
case 0x6422007C: /* external [Atelier Ryza (PC)] */
|
||||
case 0x793A1FD7: /* external [Stranger of Paradise (PS4)]-encrypted */
|
||||
case 0xA0F4FC6C: /* external [Stranger of Paradise (PS4)]-encrypted */
|
||||
/* 08 subtype? (ex. 0x522B86B9)
|
||||
* 0c channels
|
||||
* 10 ? (always 0x002706B8)
|
||||
* 10 ? (always 0x002706B8 / 7864523D in SoP)
|
||||
* 14 external codec
|
||||
* 18 sample rate
|
||||
* 1c num samples
|
||||
|
@ -420,16 +490,53 @@ fail:
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* ktsr engine reads+decrypts in the same func based on passed flag tho (reversed from exe)
|
||||
* Strings are usually ASCII but Shift-JIS is used in rare cases (0x0c3e2edf.srsa) */
|
||||
static void decrypt_string_ktsr(char* buf, size_t buf_size, uint32_t seed) {
|
||||
for (int i = 0; i < buf_size - 1; i++) {
|
||||
if (!buf[i]) /* just in case */
|
||||
break;
|
||||
|
||||
seed = 0x343FD * seed + 0x269EC3; /* classic rand */
|
||||
buf[i] ^= (seed >> 16) & 0xFF; /* 3rd byte */
|
||||
if (!buf[i]) /* end null is also encrypted (but there are extra nulls after it anyway) */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* like read_string but allow any value since it can be encrypted */
|
||||
static size_t read_string_ktsr(char* buf, size_t buf_size, off_t offset, STREAMFILE* sf) {
|
||||
int pos;
|
||||
|
||||
for (pos = 0; pos < buf_size; pos++) {
|
||||
uint8_t byte = read_u8(offset + pos, sf);
|
||||
char c = (char)byte;
|
||||
if (buf) buf[pos] = c;
|
||||
if (c == '\0')
|
||||
return pos;
|
||||
if (pos+1 == buf_size) {
|
||||
if (buf) buf[pos] = '\0';
|
||||
return buf_size;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void build_name(ktsr_header* ktsr, STREAMFILE* sf) {
|
||||
char sound_name[255] = {0};
|
||||
char config_name[255] = {0};
|
||||
|
||||
/* names can be different or same but usually config is better */
|
||||
if (ktsr->sound_name_offset) {
|
||||
read_string(sound_name, sizeof(sound_name), ktsr->sound_name_offset, sf);
|
||||
read_string_ktsr(sound_name, sizeof(sound_name), ktsr->sound_name_offset, sf);
|
||||
if (ktsr->sound_flags & 0x0008)
|
||||
decrypt_string_ktsr(sound_name, sizeof(sound_name), ktsr->audio_id);
|
||||
}
|
||||
if (ktsr->config_name_offset) {
|
||||
read_string(config_name, sizeof(config_name), ktsr->config_name_offset, sf);
|
||||
read_string_ktsr(config_name, sizeof(config_name), ktsr->config_name_offset, sf);
|
||||
if (ktsr->config_flags & 0x0200)
|
||||
decrypt_string_ktsr(config_name, sizeof(config_name), ktsr->audio_id);
|
||||
}
|
||||
|
||||
//if (longname[0] && shortname[0]) {
|
||||
|
@ -445,22 +552,24 @@ static void build_name(ktsr_header* ktsr, STREAMFILE* sf) {
|
|||
|
||||
}
|
||||
|
||||
static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id) {
|
||||
static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf) {
|
||||
/* more configs than sounds is possible so we need target_id first */
|
||||
uint32_t offset, end, name_offset;
|
||||
uint32_t stream_id;
|
||||
|
||||
offset = 0x40;
|
||||
end = get_streamfile_size(sf);
|
||||
offset = 0x40 + ktsr->base_offset;
|
||||
end = get_streamfile_size(sf) - ktsr->base_offset;
|
||||
while (offset < end) {
|
||||
uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */
|
||||
uint32_t size = read_u32le(offset + 0x04, sf);
|
||||
switch(type) {
|
||||
case 0xBD888C36: /* config */
|
||||
stream_id = read_u32be(offset + 0x08, sf);
|
||||
if (stream_id != target_id)
|
||||
stream_id = read_u32le(offset + 0x08, sf);
|
||||
if (stream_id != ktsr->sound_id)
|
||||
break;
|
||||
|
||||
ktsr->config_flags = read_u32le(offset + 0x0c, sf);
|
||||
|
||||
name_offset = read_u32le(offset + 0x28, sf);
|
||||
if (name_offset > 0)
|
||||
ktsr->config_name_offset = offset + name_offset;
|
||||
|
@ -476,14 +585,14 @@ static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id
|
|||
|
||||
static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
|
||||
uint32_t offset, end, header_offset, name_offset;
|
||||
uint32_t stream_id = 0, stream_count;
|
||||
uint32_t stream_count;
|
||||
|
||||
/* 00: KTSR
|
||||
* 04: type
|
||||
* 08: version?
|
||||
* 0a: unknown (usually 00, 02/03 seen in Vita)
|
||||
* 0b: platform (01=PC, 03=Vita, 04=Switch)
|
||||
* 0c: game id?
|
||||
* 0c: audio id? (seen in multiple files/games and used as Ogg stream IDs)
|
||||
* 10: null
|
||||
* 14: null
|
||||
* 18: file size
|
||||
|
@ -491,25 +600,28 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
|
|||
* up to 40: reserved
|
||||
* until end: entries (totals not defined) */
|
||||
|
||||
ktsr->platform = read_u8(0x0b,sf);
|
||||
ktsr->platform = read_u8(ktsr->base_offset + 0x0b,sf);
|
||||
ktsr->audio_id = read_u32le(ktsr->base_offset + 0x0c,sf);
|
||||
|
||||
if (read_u32le(0x18, sf) != read_u32le(0x1c, sf))
|
||||
if (read_u32le(ktsr->base_offset + 0x18, sf) != read_u32le(ktsr->base_offset + 0x1c, sf))
|
||||
goto fail;
|
||||
if (read_u32le(0x1c, sf) != get_streamfile_size(sf))
|
||||
if (read_u32le(ktsr->base_offset + 0x1c, sf) != get_streamfile_size(sf) - ktsr->base_offset) {
|
||||
vgm_logi("KTSR: incorrect file size (bad rip?)\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
offset = 0x40;
|
||||
end = get_streamfile_size(sf);
|
||||
offset = 0x40 + ktsr->base_offset;
|
||||
end = get_streamfile_size(sf) - ktsr->base_offset;
|
||||
while (offset < end) {
|
||||
uint32_t type = read_u32be(offset + 0x00, sf); /* hash-id? */
|
||||
uint32_t size = read_u32le(offset + 0x04, sf);
|
||||
|
||||
/* parse chunk-like subfiles, usually N configs then N songs */
|
||||
switch(type) {
|
||||
case 0x6172DBA8: /* padding (empty) */
|
||||
case 0xBD888C36: /* config (floats, stream id, etc, may have extended name) */
|
||||
case 0x6172DBA8: /* ? (mostly empty) */
|
||||
case 0xBD888C36: /* cue? (floats, stream id, etc, may have extended name; can have sub-chunks)-appears N times */
|
||||
case 0xC9C48EC1: /* unknown (has some string inside like "boss") */
|
||||
case 0xA9D23BF1: /* "state container", some kind of config/floats, witgh names like 'State_bgm01'..N */
|
||||
case 0xA9D23BF1: /* "state container", some kind of config/floats, with names like 'State_bgm01'..N */
|
||||
case 0x836FBECA: /* unknown (~0x300, encrypted? table + data) */
|
||||
break;
|
||||
|
||||
|
@ -517,9 +629,10 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
|
|||
ktsr->total_subsongs++;
|
||||
|
||||
/* sound table:
|
||||
* 08: stream id (used in several places)
|
||||
* 0c: unknown (low number but not version?)
|
||||
* 0e: external flag
|
||||
* 08: current/stream id (used in several places)
|
||||
* 0c: flags (sounds only; other types are similar but different bits)
|
||||
* 0x08: encrypted names (only used after ASRS was introduced?)
|
||||
* 0x10000: external flag
|
||||
* 10: sub-streams?
|
||||
* 14: offset to header offset
|
||||
* 18: offset to name
|
||||
|
@ -528,11 +641,10 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
|
|||
* --: header
|
||||
* --: subheader (varies) */
|
||||
|
||||
|
||||
if (ktsr->total_subsongs == ktsr->target_subsong) {
|
||||
|
||||
stream_id = read_u32be(offset + 0x08,sf);
|
||||
//ktsr->is_external = read_u16le(offset + 0x0e,sf);
|
||||
ktsr->sound_id = read_u32le(offset + 0x08,sf);
|
||||
ktsr->sound_flags = read_u32le(offset + 0x0c,sf);
|
||||
stream_count = read_u32le(offset + 0x10,sf);
|
||||
if (stream_count != 1) {
|
||||
VGM_LOG("ktsr: unknown stream count\n");
|
||||
|
@ -563,9 +675,18 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) {
|
|||
if (ktsr->target_subsong > ktsr->total_subsongs)
|
||||
goto fail;
|
||||
|
||||
parse_longname(ktsr, sf, stream_id);
|
||||
parse_longname(ktsr, sf);
|
||||
build_name(ktsr, sf);
|
||||
|
||||
/* skip TSRS header (internals are pre-adjusted) */
|
||||
if (ktsr->is_external && ktsr->base_offset) {
|
||||
for (int i = 0; i < ktsr->channels; i++) {
|
||||
ktsr->stream_offsets[i] += ktsr->base_offset;
|
||||
}
|
||||
|
||||
ktsr->extra_offset += ktsr->base_offset; /* ? */
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
vgm_logi("KTSR: unknown variation (report)\n");
|
||||
|
|
152
Frameworks/vgmstream/vgmstream/src/meta/ktsr_streamfile.h
Normal file
152
Frameworks/vgmstream/vgmstream/src/meta/ktsr_streamfile.h
Normal file
|
@ -0,0 +1,152 @@
|
|||
#ifndef _KTSR_STREAMFILE_H_
|
||||
#define _KTSR_STREAMFILE_H_
|
||||
|
||||
#include "../streamfile.h"
|
||||
#include "../util/log.h"
|
||||
#include "../util/cipher_blowfish.h"
|
||||
|
||||
/* decrypts blowfish in realtime (as done by games) */
|
||||
typedef struct {
|
||||
uint8_t key[0x20];
|
||||
uint8_t block[0x08];
|
||||
blowfish_ctx* ctx;
|
||||
} ktsr_io_data;
|
||||
|
||||
static int ktsr_io_init(STREAMFILE* sf, ktsr_io_data* data) {
|
||||
/* ktsr keys start with size then random bytes (usually 7), assumed max 0x20 */
|
||||
if (data->key[0] >= sizeof(data->key) - 1)
|
||||
return -1;
|
||||
|
||||
data->ctx = blowfish_init_ecb(data->key + 1, data->key[0]);
|
||||
if (!data->ctx)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ktsr_io_close(STREAMFILE* sf, ktsr_io_data* data) {
|
||||
blowfish_free(data->ctx);
|
||||
}
|
||||
|
||||
|
||||
static int read_part(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, ktsr_io_data* data) {
|
||||
|
||||
off_t offset_rem = offset % 0x08;
|
||||
offset -= offset_rem;
|
||||
|
||||
if (offset_rem == 0 && length >= 0x08) /* handled in main */
|
||||
return 0;
|
||||
|
||||
/* read one full block, regardless of requested length */
|
||||
int bytes = read_streamfile(data->block, offset, 0x08, sf);
|
||||
|
||||
/* useless since KTSR data is padded and blocks don't work otherwise but for determinability */
|
||||
if (bytes < 0x08)
|
||||
memset(data->block + bytes, 0, 0x08 - bytes);
|
||||
|
||||
blowfish_decrypt_ecb(data->ctx, data->block);
|
||||
|
||||
int max_copy = bytes - offset_rem;
|
||||
if (max_copy > length)
|
||||
max_copy = length;
|
||||
|
||||
memcpy(dest, data->block + offset_rem, max_copy);
|
||||
|
||||
return max_copy;
|
||||
}
|
||||
|
||||
static int read_main(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, ktsr_io_data* data) {
|
||||
int read = 0;
|
||||
|
||||
off_t offset_rem = offset % 0x08;
|
||||
size_t length_rem = length % 0x08;
|
||||
length -= length_rem;
|
||||
|
||||
if (offset_rem != 0 || length == 0) /* handled in part */
|
||||
return 0;
|
||||
|
||||
int bytes = read_streamfile(dest, offset, length, sf);
|
||||
|
||||
while (read < bytes) {
|
||||
blowfish_decrypt_ecb(data->ctx, dest);
|
||||
dest += 0x08;
|
||||
read += 0x08;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/* blowfish is a 64-bit block cipher, so arbitrary reads will need to handle partial cases. ex
|
||||
* - reading 0x00 to 0x20: direct decrypt (4 blocks of 0x08)
|
||||
* - reading 0x03 to 0x07: decrypt 0x00 to 0x08 but copy 4 bytes at 0x03
|
||||
* - reading 0x03 to 0x22: handle as 0x00 to 0x08 (head, copy 5 at 0x3), 0x08 to 0x20 (body, direct), and 0x20 to 0x28 (tail, copy 2 at 0x0). */
|
||||
static size_t ktsr_io_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, ktsr_io_data* data) {
|
||||
int bytes = 0;
|
||||
|
||||
|
||||
/* head */
|
||||
if (length) {
|
||||
int done = read_part(sf, dest, offset, length, data);
|
||||
dest += done;
|
||||
offset += done;
|
||||
length -= done;
|
||||
|
||||
bytes += done;
|
||||
}
|
||||
|
||||
/* body */
|
||||
if (length) {
|
||||
int done = read_main(sf, dest, offset, length, data);
|
||||
dest += done;
|
||||
offset += done;
|
||||
length -= done;
|
||||
|
||||
bytes += done;
|
||||
}
|
||||
|
||||
/* tail */
|
||||
if (length) {
|
||||
int done = read_part(sf, dest, offset, length, data);
|
||||
dest += done;
|
||||
offset += done;
|
||||
length -= done;
|
||||
|
||||
bytes += done;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Decrypts blowfish KTSR streams */
|
||||
static STREAMFILE* setup_ktsr_streamfile(STREAMFILE* sf, bool is_external, uint32_t subfile_offset, uint32_t subfile_size, const char* extension) {
|
||||
STREAMFILE* new_sf = NULL;
|
||||
ktsr_io_data io_data = {0};
|
||||
|
||||
if (is_external) {
|
||||
uint32_t offset = 0x00;
|
||||
if (is_id32be(0x00, sf, "TSRS"))
|
||||
offset += 0x10;
|
||||
if (!is_id32be(offset + 0x00, sf, "KTSR"))
|
||||
goto fail;
|
||||
|
||||
read_streamfile(io_data.key, offset + 0x20, sizeof(io_data.key), sf);
|
||||
}
|
||||
|
||||
/* setup subfile */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
|
||||
/* no apparent flag other than key at offset. Only data at subfile is encrypted, so this reader assumes it will be clamped */
|
||||
if (io_data.key[0] != 0)
|
||||
new_sf = open_io_streamfile_ex_f(new_sf, &io_data, sizeof(ktsr_io_data), ktsr_io_read, NULL, ktsr_io_init, ktsr_io_close);
|
||||
|
||||
new_sf = open_clamp_streamfile_f(new_sf, subfile_offset, subfile_size);
|
||||
if (extension)
|
||||
new_sf = open_fakename_streamfile_f(new_sf, NULL, extension);
|
||||
|
||||
return new_sf;
|
||||
fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -26,8 +26,8 @@ typedef struct {
|
|||
//off_t name_offset;
|
||||
} kwb_header;
|
||||
|
||||
static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b);
|
||||
static int parse_xws(kwb_header* kwb, STREAMFILE* sf);
|
||||
static bool parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b);
|
||||
static bool parse_xws(kwb_header* kwb, STREAMFILE* sf);
|
||||
static VGMSTREAM* init_vgmstream_koei_wavebank(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b);
|
||||
|
||||
|
||||
|
@ -43,14 +43,14 @@ VGMSTREAM* init_vgmstream_kwb(STREAMFILE* sf) {
|
|||
if (!is_id32be(0x00, sf, "WBD_") &&
|
||||
!is_id32le(0x00, sf, "WBD_") &&
|
||||
!is_id32be(0x00, sf, "WHD1"))
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
/* .wbd+wbh: common [Bladestorm Nightmare (PC)]
|
||||
* .wbd+whd: uncommon [Nights of Azure 2 (PS4)]
|
||||
* .wb2+wh2: newer [Nights of Azure 2 (PC)]
|
||||
* .sed: mixed header+data [Dissidia NT (PC)] */
|
||||
if (!check_extensions(sf, "wbd,wb2,sed"))
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
|
||||
/* open companion header */
|
||||
|
@ -102,13 +102,13 @@ VGMSTREAM* init_vgmstream_xws(STREAMFILE* sf) {
|
|||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "xws"))
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
kwb.target_subsong = target_subsong;
|
||||
|
||||
if (!parse_xws(&kwb, sf))
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
vgmstream = init_vgmstream_koei_wavebank(&kwb, sf, sf);
|
||||
if (!vgmstream) goto fail;
|
||||
|
@ -119,6 +119,34 @@ fail:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* SND - Sound? from Koei games [Ninja Gaiden Sigma -Master Collection- (PC)] */
|
||||
VGMSTREAM* init_vgmstream_snd_koei(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
kwb_header kwb = {0};
|
||||
int target_subsong = sf->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .snd: header id/assumed by extractors? (doatool) */
|
||||
if (!check_extensions(sf, "snd"))
|
||||
return NULL;
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
kwb.target_subsong = target_subsong;
|
||||
|
||||
if (!parse_xws(&kwb, sf))
|
||||
return NULL;
|
||||
|
||||
vgmstream = init_vgmstream_koei_wavebank(&kwb, sf, sf);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static VGMSTREAM* init_vgmstream_koei_wavebank(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
|
@ -563,7 +591,7 @@ fail:
|
|||
}
|
||||
|
||||
|
||||
static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
|
||||
static bool parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
|
||||
off_t head_offset, body_offset, start;
|
||||
uint32_t type;
|
||||
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
|
||||
|
@ -644,12 +672,12 @@ static int parse_kwb(kwb_header* kwb, STREAMFILE* sf_h, STREAMFILE* sf_b) {
|
|||
if (!kwb->found)
|
||||
goto fail;
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
fail:
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
static int parse_type_msfbank(kwb_header* kwb, off_t offset, STREAMFILE* sf) {
|
||||
static bool parse_type_msfbank(kwb_header* kwb, off_t offset, STREAMFILE* sf) {
|
||||
/* this is just like XWSF, abridged: */
|
||||
int entries, current_subsongs, relative_subsong;
|
||||
off_t header_offset;
|
||||
|
@ -671,9 +699,9 @@ static int parse_type_msfbank(kwb_header* kwb, off_t offset, STREAMFILE* sf) {
|
|||
|
||||
kwb->stream_offset += offset;
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
//fail:
|
||||
// return 0;
|
||||
// return false;
|
||||
}
|
||||
|
||||
static int parse_type_xwsfile(kwb_header* kwb, uint32_t offset, STREAMFILE* sf) {
|
||||
|
@ -681,22 +709,22 @@ static int parse_type_xwsfile(kwb_header* kwb, uint32_t offset, STREAMFILE* sf)
|
|||
int i, chunks, chunks2;
|
||||
uint32_t (*read_u32)(off_t,STREAMFILE*) = NULL;
|
||||
|
||||
|
||||
if (!(is_id64be(offset + 0x00, sf, "XWSFILE\0")) &&
|
||||
!(is_id64be(offset + 0x00, sf, "tdpack\0\0")))
|
||||
if (!( is_id64be(offset + 0x00, sf, "XWSFILE\0") ||
|
||||
is_id64be(offset + 0x00, sf, "tdpack\0\0")))
|
||||
//is_id64be(offset + 0x00, sf, "SND\0\0\0\0\0")
|
||||
goto fail;
|
||||
|
||||
kwb->big_endian = read_u8(offset + 0x08, sf) == 0xFF;
|
||||
/* 0x0a: version? (0100: NG2/NG3 PS3, 0101: DoA LR PC, NG2/3 PC) */
|
||||
/* 0x0a: version? (0100: NG2/NG3 PS3, NGm PC, 0101: DoA LR PC, NG2/3 PC) */
|
||||
|
||||
read_u32 = kwb->big_endian ? read_u32be : read_u32le;
|
||||
|
||||
/* 0x0c: tables start */
|
||||
/* 0x10: file size */
|
||||
chunks = read_u32(offset + 0x14, sf);
|
||||
chunks2 = read_u32(offset + 0x18, sf);
|
||||
chunks2 = read_u32(offset + 0x14, sf);
|
||||
chunks = read_u32(offset + 0x18, sf);
|
||||
/* 0x1c: null */
|
||||
if (chunks != chunks2)
|
||||
if (chunks != chunks2) //seen in NGm PC
|
||||
goto fail;
|
||||
|
||||
table1_offset = read_u32(offset + 0x20, sf); /* offsets */
|
||||
|
@ -737,6 +765,13 @@ static int parse_type_xwsfile(kwb_header* kwb, uint32_t offset, STREAMFILE* sf)
|
|||
if (!parse_type_xwsfile(kwb, head_offset, sf))
|
||||
goto fail;
|
||||
}
|
||||
#if 0
|
||||
else if (entry_type == get_id32be("tdpa")) { /* + "ck\0\0" */
|
||||
i += 1;
|
||||
if (!parse_type_xwsfile(kwb, head_offset, sf))
|
||||
goto fail;
|
||||
}
|
||||
#endif
|
||||
else if (entry_type == get_id32be("CUEB") || entry_type < 0x100) {
|
||||
i += 1;
|
||||
/* CUE-like info (may start with 0 or a low number instead) */
|
||||
|
@ -767,7 +802,7 @@ fail:
|
|||
}
|
||||
|
||||
|
||||
static int parse_xws(kwb_header* kwb, STREAMFILE* sf) {
|
||||
static bool parse_xws(kwb_header* kwb, STREAMFILE* sf) {
|
||||
|
||||
/* Format is similar to WHD1 with some annoyances of its own. Variations:
|
||||
* - XWSFILE w/ N chunks: CUE offsets + 1 MSFBANK offset
|
||||
|
@ -776,16 +811,16 @@ static int parse_xws(kwb_header* kwb, STREAMFILE* sf) {
|
|||
* [Dead or Alive 5 Last Round (PC)]
|
||||
* - tdpack: same but points to N XWSFILE
|
||||
* [Ninja Gaiden 3 Razor's Edge (PS3)]
|
||||
* - SND: similar to XWSFILE w/ 2*N chunks, points to tdpack (which point to _HBW0000+KWB2)
|
||||
* [Ninja Gaiden Sigma -Master Collection- (PC)]
|
||||
*
|
||||
* Needs to call sub-parts multiple times to fill total subsongs when parsing xwsfile.
|
||||
*/
|
||||
if (!parse_type_xwsfile(kwb, 0x00, sf))
|
||||
goto fail;
|
||||
return false;
|
||||
|
||||
if (!kwb->found)
|
||||
goto fail;
|
||||
return false;
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -234,7 +234,7 @@ VGMSTREAM* init_vgmstream_rstm_rockstar(STREAMFILE* sf);
|
|||
|
||||
VGMSTREAM * init_vgmstream_acm(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ps2_kces(STREAMFILE * streamFile);
|
||||
VGMSTREAM* init_vgmstream_vig_kces(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_hxd(STREAMFILE* sf);
|
||||
|
||||
|
@ -300,7 +300,7 @@ VGMSTREAM* init_vgmstream_mus_krome(STREAMFILE* sf);
|
|||
|
||||
VGMSTREAM * init_vgmstream_rsd(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_dc_asd(STREAMFILE * streamFile);
|
||||
VGMSTREAM* init_vgmstream_asd_naxat(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_spsd(STREAMFILE* sf);
|
||||
|
||||
|
@ -344,9 +344,9 @@ VGMSTREAM * init_vgmstream_thp(STREAMFILE *streamFile);
|
|||
|
||||
VGMSTREAM* init_vgmstream_sts(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ps2_p2bt(STREAMFILE *streamFile);
|
||||
VGMSTREAM* init_vgmstream_p2bt_move_visa(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ps2_gbts(STREAMFILE *streamFile);
|
||||
VGMSTREAM* init_vgmstream_gbts(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_wii_sng(STREAMFILE *streamFile);
|
||||
|
||||
|
@ -500,7 +500,7 @@ VGMSTREAM * init_vgmstream_ps2_wmus(STREAMFILE* streamFile);
|
|||
|
||||
VGMSTREAM * init_vgmstream_hyperscan_kvag(STREAMFILE* streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ios_psnd(STREAMFILE* streamFile);
|
||||
VGMSTREAM* init_vgmstream_psnd(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_adp_wildfire(STREAMFILE* sf);
|
||||
|
||||
|
@ -523,7 +523,7 @@ VGMSTREAM * init_vgmstream_ps2_hsf(STREAMFILE* streamFile);
|
|||
|
||||
VGMSTREAM * init_vgmstream_ivag(STREAMFILE* streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ps2_2pfs(STREAMFILE* streamFile);
|
||||
VGMSTREAM* init_vgmstream_2pfs(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ubi_ckd(STREAMFILE* streamFile);
|
||||
|
||||
|
@ -580,6 +580,7 @@ VGMSTREAM * init_vgmstream_mc3(STREAMFILE *streamFile);
|
|||
|
||||
VGMSTREAM* init_vgmstream_ghs(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_s_p_sth(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_s_pack(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_aac_triace(STREAMFILE* sf);
|
||||
|
||||
|
@ -618,7 +619,6 @@ VGMSTREAM* init_vgmstream_opus_nop(STREAMFILE* sf);
|
|||
VGMSTREAM* init_vgmstream_opus_shinen(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_opus_nus3(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_opus_sps_n1(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_opus_nxa(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_opus_opusx(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_opus_prototype(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_opus_opusnx(STREAMFILE* sf);
|
||||
|
@ -651,6 +651,18 @@ VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE * streamFile);
|
|||
VGMSTREAM * init_vgmstream_ea_sbr(STREAMFILE * streamFile);
|
||||
VGMSTREAM * init_vgmstream_ea_sbr_harmony(STREAMFILE * streamFile);
|
||||
|
||||
typedef struct {
|
||||
STREAMFILE* sf_head;
|
||||
STREAMFILE* sf_body;
|
||||
uint32_t head_offset;
|
||||
uint32_t body_offset;
|
||||
meta_t type;
|
||||
bool standalone;
|
||||
bool is_sps;
|
||||
} eaac_meta_t;
|
||||
|
||||
VGMSTREAM* load_vgmstream_ea_eaac(eaac_meta_t* info);
|
||||
|
||||
VGMSTREAM* init_vgmstream_vid1(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_flx(STREAMFILE * streamFile);
|
||||
|
@ -740,8 +752,6 @@ VGMSTREAM * init_vgmstream_sdf(STREAMFILE *streamFile);
|
|||
|
||||
VGMSTREAM * init_vgmstream_svg(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_vis(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_vai(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_aif_asobo(STREAMFILE *streamFile);
|
||||
|
@ -758,6 +768,8 @@ VGMSTREAM * init_vgmstream_derf(STREAMFILE *streamFile);
|
|||
|
||||
VGMSTREAM * init_vgmstream_utk(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM* init_vgmstream_nxa1(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_adpcm_capcom(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ue4opus(STREAMFILE *streamFile);
|
||||
|
@ -818,8 +830,8 @@ VGMSTREAM * init_vgmstream_bwav(STREAMFILE * streamFile);
|
|||
VGMSTREAM * init_vgmstream_awb(STREAMFILE * streamFile);
|
||||
VGMSTREAM * init_vgmstream_awb_memory(STREAMFILE * streamFile, STREAMFILE *acbFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_acb(STREAMFILE * streamFile);
|
||||
void load_acb_wave_name(STREAMFILE *acbFile, VGMSTREAM* vgmstream, int waveid, int port, int is_memory);
|
||||
VGMSTREAM* init_vgmstream_acb(STREAMFILE* sf);
|
||||
void load_acb_wave_info(STREAMFILE *acbFile, VGMSTREAM* vgmstream, int waveid, int port, int is_memory, int load_loops);
|
||||
|
||||
VGMSTREAM * init_vgmstream_rad(STREAMFILE * streamFile);
|
||||
|
||||
|
@ -845,6 +857,7 @@ VGMSTREAM * init_vgmstream_nub_xma(STREAMFILE *streamFile);
|
|||
VGMSTREAM * init_vgmstream_nub_dsp(STREAMFILE * streamFile);
|
||||
VGMSTREAM * init_vgmstream_nub_idsp(STREAMFILE * streamFile);
|
||||
VGMSTREAM * init_vgmstream_nub_is14(STREAMFILE * streamFile);
|
||||
VGMSTREAM* init_vgmstream_nub_caf(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_xwv_valve(STREAMFILE* sf);
|
||||
|
||||
|
@ -883,6 +896,7 @@ VGMSTREAM* init_vgmstream_diva(STREAMFILE* sf);
|
|||
VGMSTREAM* init_vgmstream_imuse(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf);
|
||||
VGMSTREAM* init_vgmstream_asrs(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_mups(STREAMFILE* sf);
|
||||
|
||||
|
@ -982,4 +996,10 @@ VGMSTREAM* init_vgmstream_squeaksample(STREAMFILE* sf);
|
|||
|
||||
VGMSTREAM* init_vgmstream_snds(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_nxof(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_gwb_gwd(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM* init_vgmstream_cbx(STREAMFILE* sf);
|
||||
|
||||
#endif /*_META_H*/
|
||||
|
|
|
@ -37,8 +37,9 @@ VGMSTREAM* init_vgmstream_mpeg(STREAMFILE* sf) {
|
|||
* .mus: Marc Ecko's Getting Up (PC)
|
||||
* .imf: Colors (Gizmondo)
|
||||
* .aix: Classic Compendium 2 (Gizmondo)
|
||||
* .wav/lwav: The Seventh Seal (PC)
|
||||
* (extensionless): Interstellar Flames 2 (Gizmondo) */
|
||||
if (!check_extensions(sf, "mp3,mp2,lmp3,lmp2,mus,imf,aix,,"))
|
||||
if (!check_extensions(sf, "mp3,mp2,lmp3,lmp2,mus,imf,aix,wav,lwav,"))
|
||||
goto fail;
|
||||
|
||||
loop_flag = 0;
|
||||
|
|
|
@ -29,12 +29,12 @@ struct dsp_header {
|
|||
};
|
||||
|
||||
/* read and do basic validations to the above struct */
|
||||
static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREAMFILE* sf, int big_endian) {
|
||||
static bool read_dsp_header_endian(struct dsp_header *header, off_t offset, STREAMFILE* sf, int big_endian) {
|
||||
uint32_t (*get_u32)(const uint8_t*) = big_endian ? get_u32be : get_u32le;
|
||||
uint16_t (*get_u16)(const uint8_t*) = big_endian ? get_u16be : get_u16le;
|
||||
int16_t (*get_s16)(const uint8_t*) = big_endian ? get_s16be : get_s16le;
|
||||
int i;
|
||||
uint8_t buf[0x60];
|
||||
int zero_coefs;
|
||||
|
||||
if (offset > get_streamfile_size(sf))
|
||||
goto fail;
|
||||
|
@ -46,10 +46,13 @@ static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREA
|
|||
|
||||
/* base */
|
||||
header->sample_count = get_u32(buf+0x00);
|
||||
if (header->sample_count > 0x10000000)
|
||||
if (header->sample_count > 0x10000000 || header->sample_count == 0)
|
||||
goto fail; /* unlikely to be that big, should catch fourccs */
|
||||
|
||||
/* usually nibbles = size*2 in mono, but interleaved stereo or L+R may use nibbles =~ size (or not), so can't
|
||||
* easily reject files with more nibbles than data (nibbles may be part of the -R file) without redoing L+R handling */
|
||||
header->nibble_count = get_u32(buf+0x04);
|
||||
if (header->nibble_count > 0x10000000)
|
||||
if (header->nibble_count > 0x20000000 || header->nibble_count == 0)
|
||||
goto fail;
|
||||
|
||||
header->sample_rate = get_u32(buf+0x08);
|
||||
|
@ -70,14 +73,21 @@ static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREA
|
|||
if (header->initial_offset != 2 && header->initial_offset != 0)
|
||||
goto fail; /* Dr. Muto uses 0 */
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
zero_coefs = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
header->coef[i] = get_s16(buf+0x1c + i*0x02);
|
||||
if (header->coef[i] == 0)
|
||||
zero_coefs++;
|
||||
}
|
||||
/* some 0s are ok, more than 8 is probably wrong */
|
||||
if (zero_coefs == 16)
|
||||
goto fail;
|
||||
|
||||
header->gain = get_u16(buf+0x3c);
|
||||
if (header->gain != 0)
|
||||
goto fail;
|
||||
|
||||
/* decoder state */
|
||||
/* decoder state (could check that ps <= 0xNN but not that useful) */
|
||||
header->initial_ps = get_u16(buf+0x3e);
|
||||
header->initial_hist1 = get_s16(buf+0x40);
|
||||
header->initial_hist2 = get_s16(buf+0x42);
|
||||
|
@ -94,9 +104,9 @@ static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREA
|
|||
if (header->block_size >= 0xF000) /* same, 16b (usually 0) */
|
||||
header->block_size = 0;
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
fail:
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
static int read_dsp_header_be(struct dsp_header *header, off_t offset, STREAMFILE* file) {
|
||||
return read_dsp_header_endian(header, offset, file, 1);
|
||||
|
@ -146,16 +156,16 @@ static VGMSTREAM* init_vgmstream_dsp_common(STREAMFILE* sf, dsp_meta* dspm) {
|
|||
|
||||
|
||||
if (dspm->channels > dspm->max_channels)
|
||||
goto fail;
|
||||
if (dspm->channels > COMMON_DSP_MAX_CHANNELS)
|
||||
goto fail;
|
||||
return NULL;
|
||||
if (dspm->channels > COMMON_DSP_MAX_CHANNELS || dspm->channels < 0)
|
||||
return NULL;
|
||||
|
||||
/* load standard DSP header per channel */
|
||||
{
|
||||
for (i = 0; i < dspm->channels; i++) {
|
||||
if (!read_dsp_header_endian(&ch_header[i], dspm->header_offset + i*dspm->header_spacing, sf, !dspm->little_endian)) {
|
||||
//;VGM_LOG("DSP: bad header\n");
|
||||
goto fail;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ VGMSTREAM* init_vgmstream_nub(STREAMFILE* sf) {
|
|||
goto fail;
|
||||
|
||||
/* .nub: standard
|
||||
* .nub2: rare [iDOLM@STER - Gravure For You (PS3)] */
|
||||
* .nub2: rare [iDOLM@STER: Gravure For You (PS3), Noby Noby Boy (iOS)] */
|
||||
if (!check_extensions(sf, "nub,nub2"))
|
||||
goto fail;
|
||||
|
||||
|
@ -97,47 +97,52 @@ VGMSTREAM* init_vgmstream_nub(STREAMFILE* sf) {
|
|||
header_size = align_size_to_block(subheader_start + subheader_size, 0x10);
|
||||
|
||||
switch(codec) {
|
||||
case 0x00: /* (none) (xma1) */
|
||||
case 0x00: /* xma1 */
|
||||
fake_ext = "xma";
|
||||
init_vgmstream_function = init_vgmstream_nub_xma;
|
||||
break;
|
||||
|
||||
case 0x01: /* "wav\0" */
|
||||
case 0x01:
|
||||
fake_ext = "wav";
|
||||
init_vgmstream_function = init_vgmstream_nub_wav;
|
||||
break;
|
||||
|
||||
case 0x02: /* "vag\0" */
|
||||
case 0x02:
|
||||
fake_ext = "vag";
|
||||
init_vgmstream_function = init_vgmstream_nub_vag;
|
||||
break;
|
||||
|
||||
case 0x03: /* "at3\0" */
|
||||
case 0x03:
|
||||
fake_ext = "at3";
|
||||
init_vgmstream_function = init_vgmstream_nub_at3;
|
||||
break;
|
||||
|
||||
case 0x04: /* "xma\0" (xma2 old) */
|
||||
case 0x08: /* "xma\0" (xma2 new) */
|
||||
case 0x04: /* xma2 old */
|
||||
case 0x08: /* xma2 new */
|
||||
fake_ext = "xma";
|
||||
init_vgmstream_function = init_vgmstream_nub_xma;
|
||||
break;
|
||||
|
||||
case 0x05: /* "dsp\0" */
|
||||
case 0x05:
|
||||
fake_ext = "dsp";
|
||||
init_vgmstream_function = init_vgmstream_nub_dsp;
|
||||
break;
|
||||
|
||||
case 0x06: /* "idsp" */
|
||||
case 0x06:
|
||||
fake_ext = "idsp";
|
||||
init_vgmstream_function = init_vgmstream_nub_idsp;
|
||||
break;
|
||||
|
||||
case 0x07: /* "is14" */
|
||||
case 0x07:
|
||||
fake_ext = "is14";
|
||||
init_vgmstream_function = init_vgmstream_nub_is14;
|
||||
break;
|
||||
|
||||
case 0x09:
|
||||
fake_ext = "caf";
|
||||
init_vgmstream_function = init_vgmstream_nub_caf;
|
||||
break;
|
||||
|
||||
default:
|
||||
VGM_LOG("NUB: unknown codec %x\n", codec);
|
||||
goto fail;
|
||||
|
@ -235,9 +240,10 @@ static STREAMFILE* setup_nub_streamfile(STREAMFILE* sf, off_t header_offset, siz
|
|||
|
||||
//todo could be simplified
|
||||
|
||||
/* .nub wav - from Namco NUB archives [Ridge Racer 7 (PS3)] */
|
||||
/* .nub wav - from Namco NUB archives [Ridge Racer 7 (PS3), Noby Noby Boy (iOS)] */
|
||||
VGMSTREAM* init_vgmstream_nub_wav(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, sample_rate;
|
||||
size_t data_size, loop_start, loop_length;
|
||||
|
@ -246,10 +252,10 @@ VGMSTREAM* init_vgmstream_nub_wav(STREAMFILE* sf) {
|
|||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "wav\0"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf, "wav,lwav"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,sf) != 0x77617600) /* "wav\0" "*/
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
if (guess_endian32(0x1c, sf)) {
|
||||
read_32bit = read_32bitBE;
|
||||
|
@ -276,6 +282,21 @@ VGMSTREAM* init_vgmstream_nub_wav(STREAMFILE* sf) {
|
|||
|
||||
start_offset = 0xD0;
|
||||
|
||||
/* seen in Noby Noby Boy (iOS), not sure about flags */
|
||||
if (is_id32be(start_offset, sf, "RIFF")) {
|
||||
uint32_t subfile_offset = start_offset;
|
||||
uint32_t subfile_size = read_32bitLE(subfile_offset + 0x04, sf) + 0x08; /* RIFF size */
|
||||
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, NULL);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_riff(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
|
@ -296,6 +317,7 @@ VGMSTREAM* init_vgmstream_nub_wav(STREAMFILE* sf) {
|
|||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -310,10 +332,10 @@ VGMSTREAM* init_vgmstream_nub_vag(STREAMFILE* sf) {
|
|||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "vag\0"))
|
||||
return NULL;
|
||||
if ( !check_extensions(sf, "vag"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,sf) != 0x76616700) /* "vag\0" */
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
if (guess_endian32(0x1c, sf)) {
|
||||
read_32bit = read_32bitBE;
|
||||
|
@ -362,15 +384,13 @@ fail:
|
|||
VGMSTREAM* init_vgmstream_nub_at3(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
off_t subfile_offset = 0;
|
||||
size_t subfile_size = 0;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "at3\0"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"at3"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,sf) != 0x61743300) /* "at3\0" */
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
/* info header */
|
||||
/* 0x20: loop start (in samples) */
|
||||
|
@ -381,8 +401,8 @@ VGMSTREAM* init_vgmstream_nub_at3(STREAMFILE* sf) {
|
|||
/* format header: mini fmt (WAVEFORMATEX) + fact chunks LE (clone of RIFF's) */
|
||||
/* we can just ignore and use RIFF at data start since it has the same info */
|
||||
|
||||
subfile_offset = 0x100;
|
||||
subfile_size = read_32bitLE(subfile_offset + 0x04, sf) + 0x08; /* RIFF size */
|
||||
uint32_t subfile_offset = 0x100;
|
||||
uint32_t subfile_size = read_32bitLE(subfile_offset + 0x04, sf) + 0x08; /* RIFF size */
|
||||
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, NULL);
|
||||
if (!temp_sf) goto fail;
|
||||
|
@ -410,9 +430,9 @@ VGMSTREAM* init_vgmstream_nub_xma(STREAMFILE* sf) {
|
|||
|
||||
/* checks */
|
||||
if (!check_extensions(sf,"xma"))
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
if (read_32bitBE(0x00,sf) == 0x786D6100) { /* "xma\0" */
|
||||
if (is_id32be(0x00,sf, "xma\0")) {
|
||||
/* nub v2.1 */
|
||||
nus_codec = read_32bitBE(0x0C,sf);
|
||||
data_size = read_32bitBE(0x14,sf);
|
||||
|
@ -435,7 +455,7 @@ VGMSTREAM* init_vgmstream_nub_xma(STREAMFILE* sf) {
|
|||
chunk_size = header_size;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
start_offset = align_size_to_block(chunk_offset + header_size, 0x10);
|
||||
|
@ -520,10 +540,10 @@ VGMSTREAM* init_vgmstream_nub_dsp(STREAMFILE* sf) {
|
|||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "dsp\0"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"dsp"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,sf) != 0x64737000) /* "dsp\0" */
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
/* paste header+data together and pass to meta, which has loop info too */
|
||||
header_offset = 0xBC;
|
||||
|
@ -554,10 +574,10 @@ VGMSTREAM* init_vgmstream_nub_idsp(STREAMFILE* sf) {
|
|||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "idsp"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"idsp"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,sf) != 0x69647370) /* "idsp" */
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
/* info header */
|
||||
/* 0x20: loop start (in samples) */
|
||||
|
@ -595,10 +615,10 @@ VGMSTREAM* init_vgmstream_nub_is14(STREAMFILE* sf) {
|
|||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "is14"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"is14"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,sf) != 0x69733134) /* "is14" */
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
if (guess_endian32(0x1c, sf)) {
|
||||
read_32bit = read_32bitBE;
|
||||
|
@ -632,3 +652,41 @@ fail:
|
|||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* .nub is14 - from Namco NUB archives [Noby Noby Boy (iOS)] */
|
||||
VGMSTREAM* init_vgmstream_nub_caf(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "caf\0"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"caf"))
|
||||
return NULL;
|
||||
|
||||
/* info header */
|
||||
/* 0x20: loop start (in samples) */
|
||||
/* 0x24: loop length (in samples) */
|
||||
/* 0x28: loop flag */
|
||||
/* 0x2c: null */
|
||||
|
||||
/* format header: simplified caff with some chunks */
|
||||
/* we can just ignore and use caff at data start since it has the same info */
|
||||
|
||||
uint32_t subfile_offset = 0x110;
|
||||
uint32_t subfile_size = read_u32le(0x14, sf); /* padded but not fully validated */
|
||||
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, NULL);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_apple_caff(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* Entergram NXA Opus [Higurashi no Naku Koro ni Hou (Switch), Gensou Rougoku no Kaleidoscope (Switch)] */
|
||||
VGMSTREAM* init_vgmstream_opus_nxa(STREAMFILE* sf) {
|
||||
/* Entergram NXA1 Opus [Higurashi no Naku Koro ni Hou (Switch), Gensou Rougoku no Kaleidoscope (Switch)] */
|
||||
VGMSTREAM* init_vgmstream_nxa1(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channels, type, sample_rate;
|
||||
|
@ -11,10 +11,10 @@ VGMSTREAM* init_vgmstream_opus_nxa(STREAMFILE* sf) {
|
|||
size_t data_size, frame_size;
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "nxa"))
|
||||
goto fail;
|
||||
if (!is_id32be(0x00, sf, "NXA1"))
|
||||
goto fail;
|
||||
if (!check_extensions(sf, "nxa"))
|
||||
goto fail;
|
||||
|
||||
start_offset = 0x30;
|
||||
type = read_u32le(0x04, sf);
|
||||
|
@ -36,7 +36,7 @@ VGMSTREAM* init_vgmstream_opus_nxa(STREAMFILE* sf) {
|
|||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_NXA;
|
||||
vgmstream->meta_type = meta_NXA1;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start;
|
53
Frameworks/vgmstream/vgmstream/src/meta/nxof.c
Normal file
53
Frameworks/vgmstream/vgmstream/src/meta/nxof.c
Normal file
|
@ -0,0 +1,53 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* Nihon Falcom FDK NXOpus [Ys X -NORDICS- (Switch)] */
|
||||
VGMSTREAM* init_vgmstream_nxof(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channels, sample_rate;
|
||||
size_t data_size, skip = 0;
|
||||
int32_t num_samples, loop_start, loop_end;
|
||||
|
||||
/* checks */
|
||||
if (!is_id32le(0x00, sf, "nxof"))
|
||||
goto fail;
|
||||
if (!check_extensions(sf,"nxopus"))
|
||||
goto fail;
|
||||
|
||||
channels = read_u8(0x05, sf);
|
||||
sample_rate = read_u32le(0x08, sf);
|
||||
start_offset = read_u32le(0x18, sf);
|
||||
data_size = read_u32le(0x1C, sf);
|
||||
num_samples = read_u32le(0x20, sf);
|
||||
loop_start = read_u32le(0x30, sf);
|
||||
loop_end = read_u32le(0x34, sf);
|
||||
|
||||
loop_flag = (loop_end > 0);
|
||||
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_NXOF;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
skip = switch_opus_get_encoder_delay(start_offset, sf);
|
||||
vgmstream->codec_data = init_ffmpeg_switch_opus(sf, start_offset, data_size, vgmstream->channels, skip, vgmstream->sample_rate);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -135,8 +135,9 @@ VGMSTREAM* init_vgmstream_opus_std(STREAMFILE* sf) {
|
|||
|
||||
/* .opus: standard / .lopus: for plugins
|
||||
* .bgm: Cotton Reboot (Switch)
|
||||
* .opu: Ys Memoire: The Oath in Felghana (Switch) */
|
||||
if (!check_extensions(sf,"opus,lopus,bgm,opu"))
|
||||
* .opu: Ys Memoire: The Oath in Felghana (Switch)
|
||||
* .ogg: Trouble Witches Origin (Switch) */
|
||||
if (!check_extensions(sf,"opus,lopus,bgm,opu,ogg,logg"))
|
||||
goto fail;
|
||||
|
||||
offset = 0x00;
|
||||
|
|
65
Frameworks/vgmstream/vgmstream/src/meta/p2bt_move_visa.c
Normal file
65
Frameworks/vgmstream/vgmstream/src/meta/p2bt_move_visa.c
Normal file
|
@ -0,0 +1,65 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* P2BT/MOVE/VISA - from Konami/KCE Studio games [Pop'n Music 7/8/Best (PS2), AirForce Delta Strike (PS2)] */
|
||||
VGMSTREAM* init_vgmstream_p2bt_move_visa(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
uint32_t data_offset;
|
||||
int loop_flag, channels, sample_rate, interleave;
|
||||
uint32_t loop_start, data_size;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "P2BT") &&
|
||||
!is_id32be(0x00,sf, "MOVE") &&
|
||||
!is_id32be(0x00,sf, "VISA"))
|
||||
return NULL;
|
||||
/* .psbt/move: header id (no apparent exts)
|
||||
* .vis: actual extension found in AFDS and other KCES games */
|
||||
if (!check_extensions(sf, "p2bt,move,vis"))
|
||||
return NULL;
|
||||
|
||||
/* (header is the same with different IDs, all may be used within a single same game) */
|
||||
/* 04: 07FC? */
|
||||
sample_rate = read_s32le(0x08,sf);
|
||||
loop_start = read_s32le(0x0c,sf);
|
||||
data_size = read_u32le(0x10,sf); /* without padding */
|
||||
interleave = read_u32le(0x14,sf); /* usually 0x10, sometimes 0x400 */
|
||||
/* 18: 1? */
|
||||
/* 1c: 0x10? */
|
||||
channels = read_s32le(0x20,sf);
|
||||
/* 24: 1? */
|
||||
/* 28: stream name (AFDS), same as basename + original ext */
|
||||
|
||||
loop_flag = (loop_start != 0);
|
||||
data_offset = 0x800;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
|
||||
vgmstream->num_samples = ps_bytes_to_samples(data_size, channels);
|
||||
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channels);
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
|
||||
if (vgmstream->interleave_block_size)
|
||||
vgmstream->interleave_last_block_size = (data_size % (vgmstream->interleave_block_size * channels)) / channels;
|
||||
read_string(vgmstream->stream_name,0x10+1, 0x28, sf);
|
||||
|
||||
vgmstream->meta_type = meta_P2BT_MOVE_VISA;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, data_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
#include "meta.h"
|
||||
#include "../util.h"
|
||||
|
||||
/* GBTS : Pop'n'Music 9 Bgm File */
|
||||
|
||||
VGMSTREAM * init_vgmstream_ps2_gbts(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
char filename[PATH_LIMIT];
|
||||
|
||||
int loop_flag=0;
|
||||
int channel_count;
|
||||
off_t start_offset;
|
||||
off_t loopStart = 0;
|
||||
off_t loopEnd = 0;
|
||||
size_t filelength;
|
||||
|
||||
int i;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
if (strcasecmp("gbts",filename_extension(filename))) goto fail;
|
||||
|
||||
/* check loop */
|
||||
start_offset=0x801;
|
||||
|
||||
filelength = get_streamfile_size(streamFile);
|
||||
do {
|
||||
// Loop Start ...
|
||||
if(read_8bit(start_offset,streamFile)==0x06) {
|
||||
if(loopStart==0) loopStart = start_offset-0x801;
|
||||
}
|
||||
|
||||
// Loop End ...
|
||||
if(read_8bit(start_offset,streamFile)==0x03) {
|
||||
if(loopEnd==0) loopEnd = start_offset-0x801-0x10;
|
||||
}
|
||||
|
||||
start_offset+=0x10;
|
||||
|
||||
} while (start_offset<(int32_t)filelength);
|
||||
|
||||
loop_flag = (loopEnd!=0);
|
||||
channel_count=read_32bitLE(0x1C,streamFile);
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* fill in the vital statistics */
|
||||
vgmstream->channels = channel_count;
|
||||
vgmstream->sample_rate = read_32bitLE(0x18,streamFile);;
|
||||
|
||||
/* Check for Compression Scheme */
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->num_samples = read_32bitLE(0x0C,streamFile)/16*28/vgmstream->channels;
|
||||
vgmstream->interleave_block_size = 0x10;
|
||||
|
||||
/* Get loop point values */
|
||||
if(vgmstream->loop_flag) {
|
||||
vgmstream->loop_start_sample = (loopStart/(vgmstream->interleave_block_size)*vgmstream->interleave_block_size)/16*28;
|
||||
vgmstream->loop_start_sample += (loopStart%vgmstream->interleave_block_size)/16*28;
|
||||
vgmstream->loop_start_sample /=vgmstream->channels;
|
||||
vgmstream->loop_end_sample = (loopEnd/(vgmstream->interleave_block_size)*vgmstream->interleave_block_size)/16*28;
|
||||
vgmstream->loop_end_sample += (loopEnd%vgmstream->interleave_block_size)/16*28;
|
||||
vgmstream->loop_end_sample /=vgmstream->channels;
|
||||
}
|
||||
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->meta_type = meta_PS2_GBTS;
|
||||
|
||||
start_offset = (off_t)0x800;
|
||||
|
||||
/* open the file for reading by each channel */
|
||||
{
|
||||
for (i=0;i<channel_count;i++) {
|
||||
vgmstream->ch[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
|
||||
if (!vgmstream->ch[i].streamfile) goto fail;
|
||||
|
||||
vgmstream->ch[i].channel_start_offset=
|
||||
vgmstream->ch[i].offset=
|
||||
(off_t)(start_offset+vgmstream->interleave_block_size*i);
|
||||
}
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
#include "meta.h"
|
||||
#include "../util.h"
|
||||
|
||||
/* KCES (from Dance Dance Revolution) */
|
||||
VGMSTREAM * init_vgmstream_ps2_kces(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
char filename[PATH_LIMIT];
|
||||
off_t start_offset;
|
||||
|
||||
int loop_flag = 0;
|
||||
int channel_count;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
if (strcasecmp("kces",filename_extension(filename)) &&
|
||||
strcasecmp("vig",filename_extension(filename))) goto fail;
|
||||
|
||||
/* check header */
|
||||
if (read_32bitBE(0x00,streamFile) != 0x01006408)
|
||||
goto fail;
|
||||
|
||||
loop_flag = (read_32bitLE(0x14,streamFile)!=0);
|
||||
channel_count = read_32bitLE(0x1C,streamFile);
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* fill in the vital statistics */
|
||||
start_offset = read_32bitLE(0x08,streamFile);
|
||||
vgmstream->channels = channel_count;
|
||||
vgmstream->sample_rate = read_32bitLE(0x18,streamFile);
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->num_samples = read_32bitLE(0x0C,streamFile)*28/16/channel_count;
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = (read_32bitLE(0x0C,streamFile)-read_32bitLE(0x14,streamFile))*28/16/channel_count;
|
||||
vgmstream->loop_end_sample = read_32bitLE(0x0C,streamFile)*28/16/channel_count;
|
||||
}
|
||||
|
||||
|
||||
if(vgmstream->channels==1) {
|
||||
vgmstream->layout_type=layout_none;
|
||||
} else {
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = read_32bitLE(0x24,streamFile);
|
||||
}
|
||||
vgmstream->meta_type = meta_PS2_KCES;
|
||||
|
||||
/* open the file for reading */
|
||||
{
|
||||
int i;
|
||||
STREAMFILE * file;
|
||||
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (!file) goto fail;
|
||||
for (i=0;i<channel_count;i++) {
|
||||
vgmstream->ch[i].streamfile = file;
|
||||
|
||||
vgmstream->ch[i].channel_start_offset=
|
||||
vgmstream->ch[i].offset=start_offset+
|
||||
vgmstream->interleave_block_size*i;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
#include "meta.h"
|
||||
#include "../util.h"
|
||||
|
||||
/* P2BT : Pop'n'Music 7 & 8 Bgm File */
|
||||
|
||||
VGMSTREAM * init_vgmstream_ps2_p2bt(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
char filename[PATH_LIMIT];
|
||||
|
||||
int loop_flag=0;
|
||||
int channel_count;
|
||||
off_t start_offset;
|
||||
int i;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
if (strcasecmp("p2bt",filename_extension(filename))) goto fail;
|
||||
|
||||
if((read_32bitBE(0x00,streamFile)!=0x4d4F5645) && // MOVE
|
||||
(read_32bitBE(0x00,streamFile)!=0x50324254)) // P2BT
|
||||
goto fail;
|
||||
|
||||
/* check loop */
|
||||
loop_flag = (read_32bitLE(0x0C,streamFile)!=0);
|
||||
channel_count=read_32bitLE(0x20,streamFile);
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* fill in the vital statistics */
|
||||
vgmstream->channels = channel_count;
|
||||
vgmstream->sample_rate = read_32bitLE(0x08,streamFile);;
|
||||
|
||||
/* Check for Compression Scheme */
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->num_samples = read_32bitLE(0x10,streamFile)/16*28/vgmstream->channels;
|
||||
|
||||
/* Get loop point values */
|
||||
if(vgmstream->loop_flag) {
|
||||
vgmstream->loop_start_sample = read_32bitLE(0x0C,streamFile)/16*28/vgmstream->channels;
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
}
|
||||
|
||||
vgmstream->interleave_block_size = read_32bitLE(0x14,streamFile);;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->meta_type = meta_PS2_P2BT;
|
||||
|
||||
start_offset = (off_t)0x800;
|
||||
|
||||
/* open the file for reading by each channel */
|
||||
{
|
||||
for (i=0;i<channel_count;i++) {
|
||||
vgmstream->ch[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
|
||||
if (!vgmstream->ch[i].streamfile) goto fail;
|
||||
|
||||
vgmstream->ch[i].channel_start_offset=
|
||||
vgmstream->ch[i].offset=
|
||||
(off_t)(start_offset+vgmstream->interleave_block_size*i);
|
||||
}
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
#include "../layout/layout.h"
|
||||
|
||||
|
||||
#define PSB_MAX_LAYERS 2
|
||||
#define PSB_MAX_LAYERS 6 /* MGS Master Collection Vo.1 (Switch) */
|
||||
|
||||
typedef enum { PCM, RIFF_AT3, XMA2, MSADPCM, XWMA, DSP, OPUSNX, RIFF_AT9, VAG } psb_codec_t;
|
||||
typedef struct {
|
||||
|
|
74
Frameworks/vgmstream/vgmstream/src/meta/psnd.c
Normal file
74
Frameworks/vgmstream/vgmstream/src/meta/psnd.c
Normal file
|
@ -0,0 +1,74 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* PSND - from Polarbit games [Crash Bandicoot Nitro Kart 3D/2 (iOS), Reckless Racing 2 (Android/iOS)] */
|
||||
VGMSTREAM* init_vgmstream_psnd(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
uint32_t start_offset, data_size, type;
|
||||
int loop_flag, channels, sample_rate;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "PSND"))
|
||||
return NULL;
|
||||
/* .psn: actual extension in exes */
|
||||
if (!check_extensions(sf, "psn"))
|
||||
return NULL;
|
||||
|
||||
data_size = read_u32le(0x04, sf); /* after this field */
|
||||
type = read_u32le(0x08,sf);
|
||||
sample_rate = read_u16le(0x0c,sf);
|
||||
|
||||
switch (type) {
|
||||
case 0x0030006: /* CBNK */
|
||||
channels = read_u8(0xE,sf);
|
||||
if (read_u8(0x0f, sf) != 16) goto fail; /* bps */
|
||||
start_offset = 0x10;
|
||||
|
||||
break;
|
||||
case 0x0000004: /* RR */
|
||||
channels = 1;
|
||||
start_offset = 0x0e;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data_size = data_size + 0x08 - start_offset;
|
||||
loop_flag = 0; /* generally 22050hz music loops */
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
switch (type) {
|
||||
case 0x0030006:
|
||||
vgmstream->coding_type = coding_PCM16LE;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x02;
|
||||
vgmstream->num_samples = pcm16_bytes_to_samples(data_size, channels);
|
||||
break;
|
||||
case 0x0000004:
|
||||
vgmstream->coding_type = coding_DVI_IMA;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->num_samples = ima_bytes_to_samples(data_size, channels);
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vgmstream->loop_start_sample = 0;
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
|
||||
vgmstream->meta_type = meta_PSND;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
/* WB - from Psychonauts (PS2) */
|
||||
VGMSTREAM* init_vgmstream_pwb(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
int channels, loop_flag;
|
||||
int channels, loop_flag;
|
||||
uint32_t stream_offset, stream_size, loop_start, loop_end;
|
||||
|
||||
|
||||
|
@ -54,7 +54,7 @@ VGMSTREAM* init_vgmstream_pwb(STREAMFILE* sf) {
|
|||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
|
@ -65,7 +65,7 @@ VGMSTREAM* init_vgmstream_pwb(STREAMFILE* sf) {
|
|||
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channels);
|
||||
vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channels);
|
||||
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
|
|
|
@ -1,149 +1,357 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../util.h"
|
||||
#include "../util/endianness.h"
|
||||
|
||||
static uint32_t rotlwi(uint32_t x, uint32_t r) {
|
||||
return (x << r) | (x >> (32-r));
|
||||
}
|
||||
#define MAX_CHANNELS 4
|
||||
typedef struct {
|
||||
bool loop_flag;
|
||||
int channels;
|
||||
int sample_rate;
|
||||
int32_t num_samples;
|
||||
int32_t loop_start;
|
||||
int32_t loop_end;
|
||||
int16_t coefs[MAX_CHANNELS][16];
|
||||
|
||||
static uint32_t find_key(uint32_t firstword) {
|
||||
uint32_t expected = 0x52656453;
|
||||
return firstword ^ expected;
|
||||
}
|
||||
int total_subsongs;
|
||||
|
||||
/* RSD - RedSpark (MadWorld)
|
||||
RS3D - RedSpark (Mario & Luigi: Dream Team I fi*/
|
||||
VGMSTREAM * init_vgmstream_redspark(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
char filename[PATH_LIMIT];
|
||||
off_t start_offset;
|
||||
int loop_flag;
|
||||
int channel_count;
|
||||
int dt_flag = 0;
|
||||
uint32_t key;
|
||||
enum {encsize = 0x1000};
|
||||
uint8_t buf[encsize];
|
||||
int32_t(*get_32bit)(const uint8_t *p) = NULL;
|
||||
int16_t(*get_16bit)(const uint8_t *p) = NULL;
|
||||
get_16bit = get_16bitBE;
|
||||
get_32bit = get_32bitBE;
|
||||
uint32_t stream_offset;
|
||||
uint32_t stream_size;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
if (strcasecmp("rsd", filename_extension(filename))) goto fail;
|
||||
/* decrypt into buffer */
|
||||
{
|
||||
uint32_t data;
|
||||
int i;
|
||||
if (read_streamfile(buf,0,encsize,streamFile)!=encsize) goto fail;
|
||||
if (memcmp(&buf[0], "RedSpark", 8)) { //Check to see if already decrypted
|
||||
key = find_key(get_32bitBE(&buf[0]));
|
||||
data = get_32bitBE(&buf[0]) ^ key;
|
||||
put_32bitBE(&buf[0], data);
|
||||
key = rotlwi(key, 11);
|
||||
bool dummy;
|
||||
} redspark_header_t;
|
||||
|
||||
for (i = 4; i < encsize; i += 4) {
|
||||
key = rotlwi(key, 3) + key;
|
||||
data = get_32bitBE(&buf[i]) ^ key;
|
||||
put_32bitBE(&buf[i], data);
|
||||
}
|
||||
}
|
||||
else {
|
||||
get_16bit = get_16bitLE;
|
||||
get_32bit = get_32bitLE;
|
||||
dt_flag = 1;
|
||||
static bool parse_header(redspark_header_t* h, STREAMFILE* sf, bool is_new);
|
||||
|
||||
for (i = 4; i < encsize; i += 4) {
|
||||
data = get_32bitBE(&buf[i]);
|
||||
put_32bitBE(&buf[i], data);
|
||||
}
|
||||
}
|
||||
/* RedSpark - Games with audio by RedSpark Ltd. (Minoru Akao) [MadWorld (Wii), Imabikisou (Wii), Mario & Luigi: Dream Team (3DS)] */
|
||||
VGMSTREAM* init_vgmstream_redspark(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
bool is_new;
|
||||
|
||||
|
||||
/* checks*/
|
||||
uint32_t head_id = read_u32be(0x00, sf);
|
||||
if (head_id == get_id32be("RedS")) {
|
||||
is_new = true; /* M&L */
|
||||
}
|
||||
else if (head_id > 0x15800000 && head_id < 0x1B800000) {
|
||||
is_new = false; /* others: header is encrypted but in predictable ranges to fail faster (will do extra checks later) */
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* check header */
|
||||
if (memcmp(&buf[0],"RedSpark",8))
|
||||
goto fail;
|
||||
if (!check_extensions(sf, "rsd"))
|
||||
return NULL;
|
||||
|
||||
loop_flag = (buf[0x4f] != 0);
|
||||
channel_count = buf[0x4e];
|
||||
redspark_header_t h = {0};
|
||||
if (!parse_header(&h, sf, is_new))
|
||||
return NULL;
|
||||
|
||||
/* make sure loop info is the only two cue points */
|
||||
if (loop_flag && buf[0x4f] != 2) goto fail;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(h.channels, h.loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* fill in the vital statistics */
|
||||
start_offset = 0x1000;
|
||||
vgmstream->channels = channel_count;
|
||||
vgmstream->sample_rate = get_32bit(&buf[0x3c]);
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
if (dt_flag)
|
||||
vgmstream->num_samples = get_32bit(&buf[0x40]);
|
||||
else
|
||||
vgmstream->num_samples = get_32bit(&buf[0x40])*14;
|
||||
if (loop_flag) {
|
||||
off_t start = 0x54;
|
||||
start += channel_count*8;
|
||||
if (dt_flag) {
|
||||
vgmstream->loop_start_sample = get_32bit(&buf[start+4]);
|
||||
vgmstream->loop_end_sample = (get_32bit(&buf[start+0xc]));
|
||||
}
|
||||
else {
|
||||
vgmstream->loop_start_sample = get_32bit(&buf[start+4])*14;
|
||||
vgmstream->loop_end_sample = (get_32bit(&buf[start+0xc])+1)*14;
|
||||
}
|
||||
if (vgmstream->loop_end_sample > vgmstream->num_samples) {
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
}
|
||||
}
|
||||
|
||||
if (channel_count >= 2) {
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 8;
|
||||
} else {
|
||||
vgmstream->layout_type = layout_none;
|
||||
}
|
||||
vgmstream->meta_type = meta_REDSPARK;
|
||||
vgmstream->sample_rate = h.sample_rate;
|
||||
vgmstream->num_samples = h.num_samples;
|
||||
vgmstream->loop_start_sample = h.loop_start;
|
||||
vgmstream->loop_end_sample = h.loop_end;
|
||||
|
||||
vgmstream->num_streams = h.total_subsongs;
|
||||
vgmstream->stream_size = h.stream_size;
|
||||
|
||||
{
|
||||
off_t start = 0x54;
|
||||
int i,j;
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x08;
|
||||
|
||||
start += channel_count * 8;
|
||||
if (loop_flag) {
|
||||
start += 16;
|
||||
}
|
||||
|
||||
for (j = 0; j < channel_count; j++) {
|
||||
for (i=0;i<16;i++) {
|
||||
vgmstream->ch[j].adpcm_coef[i] =
|
||||
get_16bit(&buf[start+0x2e*j+i*2]);
|
||||
}
|
||||
for (int ch = 0; ch < h.channels; ch++) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
vgmstream->ch[ch].adpcm_coef[i] = h.coefs[ch][i];
|
||||
}
|
||||
}
|
||||
|
||||
/* open the file for reading */
|
||||
{
|
||||
int i;
|
||||
STREAMFILE * file;
|
||||
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (!file) goto fail;
|
||||
for (i=0;i<channel_count;i++) {
|
||||
vgmstream->ch[i].streamfile = file;
|
||||
|
||||
vgmstream->ch[i].channel_start_offset=
|
||||
vgmstream->ch[i].offset=start_offset + i*vgmstream->interleave_block_size;
|
||||
|
||||
}
|
||||
if (h.dummy) {
|
||||
vgmstream->num_samples = h.sample_rate;
|
||||
vgmstream->coding_type = coding_SILENCE;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, h.stream_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static uint32_t rotlwi(uint32_t x, uint32_t r) {
|
||||
return (x << r) | (x >> (32 - r));
|
||||
}
|
||||
|
||||
static uint32_t decrypt_chunk(uint8_t* buf, int buf_size, uint32_t key) {
|
||||
uint32_t data;
|
||||
int pos = 0;
|
||||
|
||||
if (key == 0) {
|
||||
/* initial key seems to only vary slightly between files though (doesn't seem to depend on size/name) */
|
||||
key = get_u32be(buf + 0x00) ^ get_id32be("RedS");
|
||||
data = get_u32be(buf + 0x00) ^ key;
|
||||
put_u32be(&buf[0], data);
|
||||
key = rotlwi(key, 11);
|
||||
|
||||
pos = 0x04;
|
||||
}
|
||||
|
||||
for (int i = pos; i < buf_size; i += 0x04) {
|
||||
key = rotlwi(key, 3) + key;
|
||||
data = get_u32be(buf + i) ^ key;
|
||||
put_u32be(buf + i, data);
|
||||
}
|
||||
|
||||
return key; /* to resume decrypting if needed */
|
||||
}
|
||||
|
||||
#define HEADER_MAX 0x2800 /* seen 0x2420 in one bank */
|
||||
|
||||
/* header is encrypted except in M&L 3DS so decrypt + handle in buffer; format
|
||||
* base 0x30 header + subheader with tables/headers depending on type */
|
||||
static bool parse_header(redspark_header_t* h, STREAMFILE* sf, bool is_new) {
|
||||
uint8_t buf[HEADER_MAX];
|
||||
/* LE on 3DS */
|
||||
get_u16_t get_u16 = !is_new ? get_u16be : get_u16le;
|
||||
get_u32_t get_u32 = !is_new ? get_u32be : get_u32le;
|
||||
uint32_t curr_key = 0x00;
|
||||
int target_subsong = sf->stream_index;
|
||||
|
||||
/* base header:
|
||||
00 "RedSpark"
|
||||
08 chunk size (usually data size, smaller for subspark)
|
||||
0c type/chunk? 00010000=streams, 00000000=bus/se
|
||||
10 ? (low number, usually same for multiple files)
|
||||
14 file id or null
|
||||
18 data offset
|
||||
1c bank flag
|
||||
1e 0909=stream, 0404=stream (MW) or RedSpark-within aka 'subspark' (Imabikisou), 0000=bus/se config (no audio)
|
||||
20 data size (usually file size, or header + subheader size for subsparks)
|
||||
24-30 null/reserved
|
||||
*/
|
||||
int base_size = 0x30;
|
||||
if (read_streamfile(buf, 0x00, base_size,sf) != base_size)
|
||||
return false;
|
||||
if (!is_new)
|
||||
curr_key = decrypt_chunk(buf, 0x30, curr_key);
|
||||
|
||||
/* get base header */
|
||||
if (get_u64be(buf + 0x00) != get_id64be("RedSpark"))
|
||||
return false;
|
||||
uint32_t data_offset = get_u32(buf + 0x18);
|
||||
uint32_t data_size = get_u32(buf + 0x20);
|
||||
int bank_flag = get_u16(buf + 0x1c);
|
||||
int type = get_u16(buf + 0x1e);
|
||||
|
||||
if (data_offset >= HEADER_MAX)
|
||||
return false;
|
||||
|
||||
|
||||
/* get subheader and prepare offsets */
|
||||
int redspark_pos;
|
||||
int sub_offset, sub_size;
|
||||
int head_pos;
|
||||
if (data_offset == 0x30) {
|
||||
/* 'subspark', seen in a few sfx in Imabikisou (earlier version of banks) */
|
||||
/* at data offset (not encrypted):
|
||||
00 always 017F0100 (LE?)
|
||||
04-10: null
|
||||
10 file id
|
||||
13 string? size
|
||||
14 encrypted string? (ends with 0)
|
||||
at subchunk_size:
|
||||
another RedSpark with bank subflag
|
||||
*/
|
||||
/* base sub-RedSpark + reset flags */
|
||||
redspark_pos = data_size;
|
||||
if (read_streamfile(buf + redspark_pos, data_size, base_size, sf) != base_size)
|
||||
return false;
|
||||
if (!is_new)
|
||||
curr_key = decrypt_chunk(buf + redspark_pos, base_size, 0); /* new header */
|
||||
|
||||
/* setup rest of header (handled below) */
|
||||
uint32_t subdata_offset = get_u32(buf + redspark_pos + 0x18);
|
||||
bank_flag = get_u16(buf + redspark_pos + 0x1c);
|
||||
type = get_u16(buf + redspark_pos + 0x1e);
|
||||
|
||||
data_offset = redspark_pos + subdata_offset;
|
||||
if (data_offset >= HEADER_MAX)
|
||||
return false;
|
||||
|
||||
sub_offset = redspark_pos + base_size;
|
||||
sub_size = data_offset - sub_offset;
|
||||
head_pos = sub_offset;
|
||||
}
|
||||
else {
|
||||
redspark_pos = 0x00;
|
||||
sub_offset = base_size;
|
||||
sub_size = data_offset - sub_offset;
|
||||
head_pos = sub_offset;
|
||||
}
|
||||
|
||||
/* read + decrypt rest of header */
|
||||
if (read_streamfile(buf + sub_offset, sub_offset, sub_size, sf) != sub_size)
|
||||
return false;
|
||||
if (!is_new)
|
||||
decrypt_chunk(buf + sub_offset, sub_size, curr_key);
|
||||
//VGM_LOGB(buf, data_offset, 0);
|
||||
|
||||
|
||||
/* bus/se config */
|
||||
if (type == 0x0000) {
|
||||
/* 30 number of entries (after data offset)
|
||||
per entry
|
||||
00 channel offset (after all entries)
|
||||
per channel at the above offset
|
||||
00 channel config (similar to channel config in streams but extended) */
|
||||
vgm_logi("RedSpark: file has no audio\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* main info */
|
||||
uint32_t coef_offset;
|
||||
if (bank_flag) {
|
||||
/* bank with N subsongs (seen in M&L sfx packs and in subsparks) */
|
||||
/* 00 data size
|
||||
0c entries (only 1 is possible)
|
||||
0e entries again
|
||||
20+ table
|
||||
per entry:
|
||||
00 absolute offset to header
|
||||
per header
|
||||
00 null?
|
||||
04 stream size
|
||||
08 sample rate (null in subspark)
|
||||
0c nibbles (old) or samples (new), (null in subspark so uses loops)
|
||||
10 stream offset (from data_offset)
|
||||
14 loop end (num samples if non-looped)
|
||||
18 loop start of -1 it not looped
|
||||
20 config?
|
||||
24 config?
|
||||
28 coefs + hists
|
||||
5c channel config?
|
||||
*/
|
||||
|
||||
h->total_subsongs = get_u16(buf + head_pos + 0x0c);
|
||||
if (!check_subsongs(&target_subsong, h->total_subsongs))
|
||||
return false;
|
||||
|
||||
int target_pos = head_pos + 0x20 + (target_subsong - 1) * 0x04;
|
||||
if (target_pos + 0x04 >= HEADER_MAX)
|
||||
return false;
|
||||
target_pos = get_u32(buf + target_pos) + redspark_pos;
|
||||
if (target_pos + 0x70 >= HEADER_MAX)
|
||||
return false;
|
||||
|
||||
h->stream_size = get_u32(buf + target_pos + 0x04);
|
||||
h->sample_rate = get_u32(buf + target_pos + 0x08);
|
||||
h->num_samples = get_u32(buf + target_pos + 0x0c);
|
||||
h->stream_offset = get_u32(buf + target_pos + 0x10) + data_offset;
|
||||
h->loop_end = get_u32(buf + target_pos + 0x14);
|
||||
h->loop_start = get_u32(buf + target_pos + 0x18);
|
||||
coef_offset = target_pos + 0x28;
|
||||
|
||||
h->channels = 1;
|
||||
h->loop_flag = (h->loop_start != -1); /* TODO: many files sound kind of odd */
|
||||
if (h->num_samples == 0)
|
||||
h->num_samples = h->loop_end;
|
||||
if (h->sample_rate == 0)
|
||||
h->sample_rate = 32000;
|
||||
|
||||
/* empty entry */
|
||||
if (h->stream_size == 0)
|
||||
h->dummy = true;
|
||||
}
|
||||
else {
|
||||
/* stream */
|
||||
/* 00 data size (after data offset)
|
||||
04 null
|
||||
08 null
|
||||
0c sample rate
|
||||
10 frames (old) or samples (new)
|
||||
14 some chunk? (0x10000, 0xc000)
|
||||
18 null
|
||||
1c null
|
||||
1e channels (usually 1-2, sometimes 4 in MW)
|
||||
1f num loop cues (2 == loop)
|
||||
20 cues again?
|
||||
21 volume?
|
||||
22 null
|
||||
24+ variable
|
||||
per channel
|
||||
00 config per channel, size 0x08 (number/panning/volume/etc?)
|
||||
per loop point
|
||||
00 chunk size?
|
||||
04 value
|
||||
per channel
|
||||
00 dsp coefs + hists (size 0x2E)
|
||||
if cues:
|
||||
00 offset?
|
||||
04 null
|
||||
per cue:
|
||||
00 name size
|
||||
01 string ("Loop Start" / "Loop End")
|
||||
*/
|
||||
|
||||
h->sample_rate = get_u32(buf + head_pos + 0x0c);
|
||||
h->num_samples = get_u32(buf + head_pos + 0x10);
|
||||
h->channels = get_u8(buf + head_pos + 0x1e);
|
||||
int loop_cues = get_u8(buf + head_pos + 0x1f);
|
||||
|
||||
head_pos += 0x24;
|
||||
|
||||
/* just in case to avoid bad reads outside buf */
|
||||
if (h->channels > MAX_CHANNELS)
|
||||
return false;
|
||||
head_pos += h->channels * 0x08;
|
||||
|
||||
h->loop_flag = (loop_cues != 0);
|
||||
if (h->loop_flag) {
|
||||
/* only two cue points */
|
||||
if (loop_cues != 0 && loop_cues != 2)
|
||||
return false;
|
||||
h->loop_start = get_u32(buf + head_pos + 0x04);
|
||||
h->loop_end = get_u32(buf + head_pos + 0x0c);
|
||||
head_pos += 0x10;
|
||||
}
|
||||
|
||||
coef_offset = head_pos;
|
||||
h->stream_offset = data_offset;
|
||||
h->stream_size = data_size;
|
||||
}
|
||||
|
||||
/* coefs from decrypted buf (could read hist but it's 0 in DSPs) */
|
||||
for (int ch = 0; ch < h->channels; ch++) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
h->coefs[ch][i] = get_u16(buf + coef_offset + 0x2e * ch + i * 2);
|
||||
}
|
||||
}
|
||||
|
||||
/* fixes */
|
||||
if (!is_new) {
|
||||
h->loop_end += 1;
|
||||
|
||||
if (bank_flag) {
|
||||
h->num_samples /= 2 * 0x8;
|
||||
h->loop_start /= 2 * 0x8;
|
||||
h->loop_end /= 2 * 0x8;
|
||||
}
|
||||
|
||||
h->num_samples *= 14;
|
||||
h->loop_start *= 14;
|
||||
h->loop_end *= 14;
|
||||
|
||||
if (h->loop_end > h->num_samples) /* needed for some files */
|
||||
h->loop_end = h->num_samples;
|
||||
}
|
||||
/* new + bank may need +1 */
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -169,6 +169,10 @@ static int read_fmt(int big_endian, STREAMFILE* sf, off_t offset, riff_fmt_chunk
|
|||
|
||||
case 0x0002: /* MSADPCM */
|
||||
if (fmt->bps == 4) {
|
||||
/* ADPCMWAVEFORMAT extra data:
|
||||
* - samples per frame (16b)
|
||||
* - num coefs (16b), always 7
|
||||
* - N x2 coefs (configurable but in practice fixed) */
|
||||
fmt->coding_type = coding_MSADPCM;
|
||||
if (!msadpcm_check_coefs(sf, fmt->offset + 0x08 + 0x14))
|
||||
goto fail;
|
||||
|
@ -180,7 +184,7 @@ static int read_fmt(int big_endian, STREAMFILE* sf, off_t offset, riff_fmt_chunk
|
|||
goto fail;
|
||||
}
|
||||
break;
|
||||
case 0x003: /* floating point PCM */
|
||||
case 0x0003: /* floating point PCM */
|
||||
if (fmt->bps == 32) {
|
||||
fmt->coding_type = coding_PCMFLOAT;
|
||||
} else {
|
||||
|
@ -190,6 +194,8 @@ static int read_fmt(int big_endian, STREAMFILE* sf, off_t offset, riff_fmt_chunk
|
|||
break;
|
||||
|
||||
case 0x0011: /* MS-IMA ADPCM [Layton Brothers: Mystery Room (iOS/Android)] */
|
||||
/* IMAADPCMWAVEFORMAT extra data:
|
||||
* - samples per frame (16b) */
|
||||
if (fmt->bps != 4) goto fail;
|
||||
fmt->coding_type = coding_MS_IMA;
|
||||
break;
|
||||
|
@ -391,8 +397,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
|
|||
* .xms: Ty the Tasmanian Tiger (Xbox)
|
||||
* .mus: Burnout Legends/Dominator (PSP)
|
||||
* .dat/ldat: RollerCoaster Tycoon 1/2 (PC)
|
||||
* .wma/lwma: SRS: Street Racing Syndicate (Xbox), Fast and the Furious (Xbox)
|
||||
*/
|
||||
if (!check_extensions(sf, "wav,lwav,xwav,mwv,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,ckd,saf,ima,nsa,pcm,xvag,ogg,logg,p1d,xms,mus,dat,ldat")) {
|
||||
if (!check_extensions(sf, "wav,lwav,xwav,mwv,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,str,at3,rws,aud,at9,ckd,saf,ima,nsa,pcm,xvag,ogg,logg,p1d,xms,mus,dat,ldat,wma,lwma")) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
@ -409,6 +416,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
|
|||
else if (codec == 0x0069 && riff_size + 0x04 == file_size)
|
||||
riff_size -= 0x04; /* [Halo 2 (PC)] (possibly bad extractor? 'Gravemind Tool') */
|
||||
|
||||
else if (codec == 0x0069 && riff_size + 0x10 == file_size)
|
||||
riff_size += 0x08; /* [Fast and the Furious (Xbox)] ("HASH" chunk + 4 byte hash) */
|
||||
|
||||
else if (codec == 0x0000 && riff_size + 0x04 == file_size)
|
||||
riff_size -= 0x04; /* [Headhunter (DC), Bomber hehhe (DC)] */
|
||||
|
||||
|
@ -457,6 +467,9 @@ VGMSTREAM* init_vgmstream_riff(STREAMFILE* sf) {
|
|||
|
||||
else if (codec == 0xFFFE && riff_size + 0x08 + 0x40 == file_size)
|
||||
file_size -= 0x40; /* [Megami no Etsubo (PSP)] (has extra padding in all files) */
|
||||
|
||||
else if (codec == 0x0011 && file_size - riff_size - 0x08 <= 0x900 && is_id32be(riff_size + 0x08, sf, "cont"))
|
||||
riff_size = file_size - 0x08; /* [Shin Megami Tensei: Imagine (PC)] (extra "cont" info 0x800/0x900 chunk) */
|
||||
}
|
||||
|
||||
/* check for truncated RIFF */
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
/* RSTM - from Rockstar games [Midnight Club 3, Bully - Canis Canim Edit (PS2)] */
|
||||
VGMSTREAM* init_vgmstream_rstm_rockstar(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
uint32_t start_offset;
|
||||
int channels, loop_flag;
|
||||
off_t stream_offset, loop_start, loop_end;
|
||||
size_t stream_size;
|
||||
int sample_rate, channels, loop_flag;
|
||||
|
||||
|
||||
/* checks */
|
||||
|
@ -14,30 +15,38 @@ VGMSTREAM* init_vgmstream_rstm_rockstar(STREAMFILE* sf) {
|
|||
|
||||
/* .rsm: in filelist
|
||||
* .rstm: header id */
|
||||
if (!check_extensions(sf,"rsm,rstm"))
|
||||
if (!check_extensions(sf, "rsm,rstm"))
|
||||
return NULL;
|
||||
|
||||
loop_flag = (read_s32le(0x24,sf) > 0);
|
||||
channels = read_s32le(0x0C,sf);
|
||||
start_offset = 0x800;
|
||||
sample_rate = read_s32le(0x08, sf);
|
||||
channels = read_s32le(0x0C, sf);
|
||||
/* 0x10-0x18 - empty padding(?) */
|
||||
stream_size = read_s32le(0x18, sf);
|
||||
loop_start = read_s32le(0x1C, sf);
|
||||
loop_end = read_s32le(0x20, sf);
|
||||
/* other loop start/ends after here? (uncommon) */
|
||||
stream_offset = 0x800;
|
||||
|
||||
//loop_flag = (read_s32le(0x24,sf) > 0);
|
||||
loop_flag = loop_end != stream_size;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels,loop_flag);
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_RSTM_ROCKSTAR;
|
||||
|
||||
vgmstream->sample_rate = read_s32le(0x08,sf);
|
||||
vgmstream->num_samples = ps_bytes_to_samples(read_u32le(0x20,sf),channels);
|
||||
vgmstream->loop_start_sample = ps_bytes_to_samples(read_u32le(0x24,sf),channels);
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = ps_bytes_to_samples(stream_size, channels);
|
||||
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channels);
|
||||
vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channels);
|
||||
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x10;
|
||||
|
||||
/* open the file for reading */
|
||||
if ( !vgmstream_open_stream(vgmstream, sf, start_offset) )
|
||||
if ( !vgmstream_open_stream(vgmstream, sf, stream_offset) )
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
VGMSTREAM* init_vgmstream_rws_809(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
bool big_endian;
|
||||
char header_name[STREAM_NAME_SIZE], stream_name[STREAM_NAME_SIZE];
|
||||
char file_name[STREAM_NAME_SIZE], header_name[STREAM_NAME_SIZE], stream_name[STREAM_NAME_SIZE];
|
||||
int channels = 0, idx, interleave, loop_flag, sample_rate = 0, total_subsongs, target_subsong = sf->stream_index;
|
||||
read_u32_t read_u32;
|
||||
off_t chunk_offset, header_offset, misc_data_offset = 0, stream_name_offset, stream_offset = 0;
|
||||
|
@ -136,7 +136,11 @@ VGMSTREAM* init_vgmstream_rws_809(STREAMFILE* sf) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%s/%s", header_name, stream_name);
|
||||
get_streamfile_basename(sf, file_name, STREAM_NAME_SIZE);
|
||||
if (strcmp(file_name, header_name) == 0)
|
||||
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%s", stream_name);
|
||||
else
|
||||
snprintf(vgmstream->stream_name, STREAM_NAME_SIZE, "%s/%s", header_name, stream_name);
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, stream_offset + 0x0C))
|
||||
goto fail;
|
||||
|
|
|
@ -40,6 +40,7 @@ typedef struct {
|
|||
size_t block_layers_size;
|
||||
|
||||
off_t coefs_offset;
|
||||
off_t hist_offset;
|
||||
|
||||
char readable_name[STREAM_NAME_SIZE];
|
||||
} rws_header;
|
||||
|
@ -200,7 +201,7 @@ VGMSTREAM* init_vgmstream_rws(STREAMFILE* sf) {
|
|||
for (i = 0; i < rws.total_layers; i++) {
|
||||
uint32_t layer_codec = 0;
|
||||
if (i+1 == rws.target_layer) {
|
||||
rws.sample_rate = read_u32(offset + 0x00, sf);
|
||||
rws.sample_rate = read_u32(offset + 0x00, sf);
|
||||
/* 0x04: config? */
|
||||
//rws.layer_size = read_u32(offset + 0x08, sf); /* same or close to usable size */
|
||||
/* 0x0c: bits per sample */
|
||||
|
@ -210,7 +211,7 @@ VGMSTREAM* init_vgmstream_rws(STREAMFILE* sf) {
|
|||
/* 0x18: null or some size? */
|
||||
rws.codec = read_u32(offset + 0x1c, sf); /* 128b uuid (32b-16b-16b-8b*8) but first 32b is enough */
|
||||
}
|
||||
layer_codec = read_u32(offset + 0x1c, sf);
|
||||
layer_codec = read_u32(offset + 0x1c, sf);
|
||||
offset += 0x2c;
|
||||
|
||||
/* DSP has an extra field per layer */
|
||||
|
@ -219,6 +220,7 @@ VGMSTREAM* init_vgmstream_rws(STREAMFILE* sf) {
|
|||
/* 0x04: approx size/loop related? (can be 0) */
|
||||
if (i+1 == rws.target_layer) {
|
||||
rws.coefs_offset = offset + 0x1c;
|
||||
rws.hist_offset = offset + 0x40;
|
||||
}
|
||||
offset += 0x60;
|
||||
}
|
||||
|
@ -322,12 +324,14 @@ VGMSTREAM* init_vgmstream_rws(STREAMFILE* sf) {
|
|||
break;
|
||||
|
||||
case 0xF86215B0: /* {F86215B0,31D5,4C29,BD,37,CD,BF,9B,D1,0C,53} DSP GC/Wii */
|
||||
/* Burnout 2 (GC), Alice in Wonderland (Wii) */
|
||||
/* Burnout 2 (GC), Alice in Wonderland (Wii), Call of Duty: Finest Hour (GC) */
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->interleave_block_size = rws.block_size / 2;
|
||||
|
||||
/* get coefs (all channels share them; also seem fixed for all RWS) */
|
||||
dsp_read_coefs_be(vgmstream, sf, rws.coefs_offset, 0);
|
||||
/* get initial sample history data (rarely used / often empty) */
|
||||
dsp_read_hist_be(vgmstream, sf, rws.hist_offset, 0);
|
||||
|
||||
vgmstream->num_samples = dsp_bytes_to_samples(stream_size, rws.channels);
|
||||
break;
|
||||
|
|
|
@ -74,27 +74,30 @@ fail:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
#define SEGMENT_MAX 3
|
||||
|
||||
/* Nippon Ichi SPS wrapper (segmented) [Penny-Punching Princess (Switch), Disgaea 4 Complete (PC)] */
|
||||
VGMSTREAM* init_vgmstream_sps_n1_segmented(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t segment_offset;
|
||||
size_t data_size, max_size;
|
||||
int loop_flag, type, sample_rate;
|
||||
int i, segment;
|
||||
|
||||
init_vgmstream_t init_vgmstream = NULL;
|
||||
const char* extension;
|
||||
segmented_layout_data* data = NULL;
|
||||
int segment_count, loop_start_segment, loop_end_segment;
|
||||
int loop_start_segment, loop_end_segment;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .at9: Penny-Punching Princess (Switch)
|
||||
type = read_u32le(0x00,sf);
|
||||
if (type > 10)
|
||||
return NULL;
|
||||
|
||||
/* .at9: Penny-Punching Princess (Switch), Labyrinth of Galleria (PC)
|
||||
* .nlsd: Disgaea 4 Complete (PC) */
|
||||
if (!check_extensions(sf, "at9,nlsd"))
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
type = read_u32le(0x00,sf);
|
||||
data_size = read_u32le(0x04,sf);
|
||||
sample_rate = read_u16le(0x08,sf);
|
||||
/* 0x0a: flag? (stereo?) */
|
||||
|
@ -113,23 +116,58 @@ VGMSTREAM* init_vgmstream_sps_n1_segmented(STREAMFILE* sf) {
|
|||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
segment_offset = 0x1c;
|
||||
if (data_size + segment_offset != get_streamfile_size(sf))
|
||||
goto fail;
|
||||
|
||||
/* segmented using 3 files (intro/loop/outro). non-segmented wrapper is the same
|
||||
* but with loop samples instead of sub-sizes */
|
||||
|
||||
uint32_t segment_offsets[SEGMENT_MAX];
|
||||
uint32_t segment_sizes[SEGMENT_MAX];
|
||||
uint32_t segment_start, offset;
|
||||
int segment_count, segment;
|
||||
|
||||
if (data_size + 0x1c == get_streamfile_size(sf)) {
|
||||
/* common */
|
||||
segment_start = 0x1c;
|
||||
offset = segment_start;
|
||||
for (int i = 0; i < SEGMENT_MAX; i++) {
|
||||
uint32_t segment_size = read_u32le(0x10 + 0x04*i,sf);
|
||||
|
||||
segment_sizes[i] = segment_size;
|
||||
segment_offsets[i] = offset;
|
||||
offset += segment_sizes[i];
|
||||
}
|
||||
}
|
||||
else if (data_size + 0x18 == get_streamfile_size(sf)) {
|
||||
/* Labyrinth of Galleria (PC) */
|
||||
segment_start = 0x18;
|
||||
offset = segment_start;
|
||||
for (int i = 0; i < SEGMENT_MAX; i++) {
|
||||
uint32_t next_offset;
|
||||
if (i >= 2) {
|
||||
next_offset = get_streamfile_size(sf) - segment_start;
|
||||
}
|
||||
else {
|
||||
next_offset = read_u32le(0x10 + 0x04*i,sf); /* only 2 (not sure if it can be 0) */
|
||||
}
|
||||
|
||||
segment_sizes[i] = next_offset - offset + segment_start;
|
||||
segment_offsets[i] = offset;
|
||||
offset += segment_sizes[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
max_size = 0;
|
||||
segment_count = 0;
|
||||
for (i = 0; i < 3; i++) {
|
||||
size_t segment_size = read_u32le(0x10 + 0x04*i,sf);
|
||||
max_size += segment_size;
|
||||
/* may only set 1 segment (Disgaea4's bgm_185) */
|
||||
if (segment_size)
|
||||
for (int i = 0; i < SEGMENT_MAX; i++) {
|
||||
/* may only set 1 segment, with empty intro/outro (Disgaea4's bgm_185) */
|
||||
if (segment_sizes[i])
|
||||
segment_count++;
|
||||
max_size += segment_sizes[i];
|
||||
}
|
||||
if (data_size != max_size)
|
||||
goto fail;
|
||||
|
@ -144,25 +182,21 @@ VGMSTREAM* init_vgmstream_sps_n1_segmented(STREAMFILE* sf) {
|
|||
|
||||
/* open each segment subfile */
|
||||
segment = 0;
|
||||
for (i = 0; i < 3; i++) {
|
||||
STREAMFILE* temp_sf;
|
||||
size_t segment_size = read_u32le(0x10 + 0x04*i,sf);
|
||||
|
||||
if (!segment_size)
|
||||
for (int i = 0; i < SEGMENT_MAX; i++) {
|
||||
if (!segment_sizes[i])
|
||||
continue;
|
||||
|
||||
temp_sf = setup_subfile_streamfile(sf, segment_offset,segment_size, extension);
|
||||
STREAMFILE* temp_sf = setup_subfile_streamfile(sf, segment_offsets[i],segment_sizes[i], extension);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
data->segments[segment] = init_vgmstream(temp_sf);
|
||||
close_streamfile(temp_sf);
|
||||
if (!data->segments[segment]) goto fail;
|
||||
|
||||
segment_offset += segment_size;
|
||||
segment++;
|
||||
|
||||
if (type == 9) {
|
||||
//todo there are some trailing samples that must be removed for smooth loops, start skip seems ok
|
||||
//TODO there are some trailing samples that must be removed for smooth loops, start skip seems ok
|
||||
//not correct for all files, no idea how to calculate
|
||||
data->segments[segment]->num_samples -= 374;
|
||||
}
|
||||
|
|
|
@ -24,14 +24,13 @@ VGMSTREAM* init_vgmstream_sqex_scd(STREAMFILE* sf) {
|
|||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "scd"))
|
||||
goto fail;
|
||||
|
||||
/** main header **/
|
||||
if (!is_id32be(0x00,sf, "SEDB") &&
|
||||
!is_id32be(0x04,sf, "SSCF"))
|
||||
goto fail;
|
||||
return NULL;
|
||||
if (!check_extensions(sf, "scd"))
|
||||
return NULL;
|
||||
|
||||
/** main header **/
|
||||
big_endian = read_u8(0x0c,sf) == 0x01;
|
||||
if (big_endian) { /* big endian flag */
|
||||
//size_offset = 0x14;
|
||||
|
@ -152,7 +151,7 @@ VGMSTREAM* init_vgmstream_sqex_scd(STREAMFILE* sf) {
|
|||
#ifdef VGM_USE_VORBIS
|
||||
/* special case using init_vgmstream_ogg_vorbis */
|
||||
if (codec == 0x06) {
|
||||
VGMSTREAM *ogg_vgmstream;
|
||||
VGMSTREAM* ogg_vgmstream;
|
||||
uint8_t ogg_version, ogg_byte;
|
||||
ogg_vorbis_meta_info_t ovmi = {0};
|
||||
|
||||
|
@ -216,15 +215,16 @@ VGMSTREAM* init_vgmstream_sqex_scd(STREAMFILE* sf) {
|
|||
read_string(vgmstream->stream_name, STREAM_NAME_SIZE, name_offset, sf);
|
||||
|
||||
switch (codec) {
|
||||
case 0x01: /* PCM */
|
||||
vgmstream->coding_type = coding_PCM16LE;
|
||||
case 0x00: /* PCM BE [Drakengard 3 (PS3)] */
|
||||
case 0x01: /* PCM LE */
|
||||
vgmstream->coding_type = codec == 0x00 ? coding_PCM16BE : coding_PCM16LE;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x02;
|
||||
|
||||
vgmstream->num_samples = pcm_bytes_to_samples(stream_size, channels, 16);
|
||||
vgmstream->num_samples = pcm16_bytes_to_samples(stream_size, channels);
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = pcm_bytes_to_samples(loop_start, channels, 16);
|
||||
vgmstream->loop_end_sample = pcm_bytes_to_samples(loop_end, channels, 16);
|
||||
vgmstream->loop_start_sample = pcm16_bytes_to_samples(loop_start, channels);
|
||||
vgmstream->loop_end_sample = pcm16_bytes_to_samples(loop_end, channels);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ VGMSTREAM * init_vgmstream_sthd(STREAMFILE *sf) {
|
|||
goto fail;
|
||||
/* first block has special values */
|
||||
if (read_u16le(0x04,sf) != 0x0800 ||
|
||||
read_u32le(0x0c,sf) != 0x0001 ||
|
||||
read_u32le(0x14,sf) != 0x0000)
|
||||
goto fail;
|
||||
|
||||
|
|
|
@ -3,19 +3,19 @@
|
|||
|
||||
/* STM - from Angel Studios/Rockstar San Diego games [Red Dead Revolver (PS2), Spy Hunter 2 (PS2/Xbox)] */
|
||||
VGMSTREAM* init_vgmstream_stma(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag = 0, channel_count;
|
||||
int big_endian, bps, interleave, data_size, loop_start = 0, loop_end = 0;
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL;
|
||||
int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "STMA") && /* LE */
|
||||
!is_id32be(0x00,sf, "AMTS")) /* BE */
|
||||
goto fail;
|
||||
/* .stm: real extension
|
||||
/* .stm: real extension
|
||||
* .lstm: for plugins */
|
||||
if (!check_extensions(sf,"stm,lstm"))
|
||||
goto fail;
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../util/layout_utils.h"
|
||||
#include "str_wav_streamfile.h"
|
||||
|
||||
|
||||
typedef enum { PSX, DSP, XBOX, WMA, IMA, XMA2 } strwav_codec;
|
||||
typedef enum { PSX, PSX_chunked, DSP, XBOX, WMA, IMA, XMA2, MPEG } strwav_codec;
|
||||
typedef struct {
|
||||
int tracks;
|
||||
int channels;
|
||||
|
@ -103,6 +105,29 @@ VGMSTREAM* init_vgmstream_str_wav(STREAMFILE* sf) {
|
|||
vgmstream->interleave_block_size = strwav.interleave;
|
||||
break;
|
||||
|
||||
case PSX_chunked: { /* hack */
|
||||
//tracks are stereo blocks of size 0x20000 * tracks, containing 4 interleaves of 0x8000:
|
||||
// | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | ...
|
||||
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->interleave_block_size = strwav.interleave;
|
||||
for (int i = 0; i < strwav.tracks; i++) {
|
||||
uint32_t chunk_size = 0x20000;
|
||||
int layer_channels = 2;
|
||||
|
||||
STREAMFILE* temp_sf = setup_str_wav_streamfile(sf, 0x00, strwav.tracks, i, chunk_size);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
bool res = layered_add_sf(vgmstream, strwav.tracks, layer_channels, temp_sf);
|
||||
close_streamfile(temp_sf);
|
||||
if (!res)
|
||||
goto fail;
|
||||
}
|
||||
if (!layered_add_done(vgmstream))
|
||||
goto fail;
|
||||
break;
|
||||
}
|
||||
|
||||
case DSP:
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
|
@ -173,6 +198,26 @@ VGMSTREAM* init_vgmstream_str_wav(STREAMFILE* sf) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef VGM_USE_MPEG
|
||||
case MPEG: {
|
||||
/* regular MP3 starting with ID2, stereo tracks xN (bgm + vocals) but assuming last (or only one) could be mono */
|
||||
int layers = (strwav.channels + 1) / 2;
|
||||
|
||||
for (int i = 0; i < layers; i++) {
|
||||
uint32_t size = strwav.interleave;
|
||||
uint32_t offset = i * size;
|
||||
const char* ext = "mp3";
|
||||
int layer_channels = ((layers % 2) && i + 1 == layers) ? 1 : 2;
|
||||
|
||||
layered_add_subfile(vgmstream, layers, layer_channels, sf, offset, size, ext, init_vgmstream_mpeg);
|
||||
}
|
||||
|
||||
if (!layered_add_done(vgmstream))
|
||||
goto fail;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
@ -286,7 +331,7 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
|
|||
/* 0x10c: header size */
|
||||
|
||||
strwav->codec = IMA;
|
||||
strwav->interleave = strwav->tracks > 1 ? 0x8000 : 0x10000;
|
||||
strwav->interleave = strwav->tracks > 1 ? 0x10000 : 0x10000;
|
||||
;VGM_LOG("STR+WAV: header TAZd (PC)\n");
|
||||
return 1;
|
||||
}
|
||||
|
@ -435,8 +480,8 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
|
|||
|
||||
strwav->codec = PSX;
|
||||
strwav->interleave = strwav->tracks > 1 ? 0x8000 : 0x8000;
|
||||
//todo: tracks are stereo blocks of size 0x20000*tracks, containing 4 interleaves of 0x8000:
|
||||
// | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | 1 2 1 2 | 3 4 3 4 | 5 6 5 6 | ...
|
||||
if (strwav->tracks > 1) /* hack */
|
||||
strwav->codec = PSX_chunked;
|
||||
;VGM_LOG("STR+WAV: header ZPb (PS2)\n");
|
||||
return 1;
|
||||
}
|
||||
|
@ -498,6 +543,32 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* Taz Wanted (beta) (PC)[2002] */
|
||||
if ( read_u32be(0x04,sf_h) == 0x00000900 &&
|
||||
read_u32le(0x0c,sf_h) != header_size &&
|
||||
read_u32le(0x24,sf_h) != 0 &&
|
||||
read_u32le(0xd4,sf_h) != 0 &&
|
||||
read_u32le(0xdc,sf_h) == header_size
|
||||
) {
|
||||
/* 0x08: null */
|
||||
/* 0x0c: hashname */
|
||||
strwav->num_samples = read_s32le(0x20,sf_h);
|
||||
strwav->sample_rate = read_s32le(0x24,sf_h);
|
||||
/* 0x28: 16 bps */
|
||||
strwav->flags = read_u32le(0x2c,sf_h);
|
||||
strwav->loop_start = read_s32le(0x38,sf_h);
|
||||
/* 0x54: number of chunks */
|
||||
strwav->tracks = read_s32le(0xd4,sf_h);
|
||||
/* 0xdc: header size */
|
||||
|
||||
strwav->loop_end = strwav->num_samples;
|
||||
|
||||
strwav->codec = IMA;
|
||||
strwav->interleave = strwav->tracks > 1 ? 0x8000 : 0x10000;
|
||||
;VGM_LOG("STR+WAV: header TAZb (PC)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Taz Wanted (PC)[2002] */
|
||||
/* Zapper: One Wicked Cricket! Beta (Xbox)[2002] */
|
||||
if ( read_u32be(0x04,sf_h) == 0x00000900 &&
|
||||
|
@ -669,7 +740,7 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
|
|||
/* 0x4c: ? (some low number) */
|
||||
strwav->tracks = read_u8 (0x4e,sf_h);
|
||||
/* 0x4f: 16 bps */
|
||||
/* 0x54: channels per each track? (ex. 2 stereo track: 0x02,0x02) */
|
||||
/* 0x54: channels per each track (ex. 2 stereo track: 0x02,0x02) */
|
||||
/* 0x64: channels */
|
||||
/* 0x70+: tables */
|
||||
|
||||
|
@ -712,6 +783,7 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
|
|||
/* Tak and the Guardians of Gross (Wii)[2008] */
|
||||
/* The House of the Dead: Overkill (Wii)[2009] (not Blitz but still the same format) */
|
||||
/* All Star Karate (Wii)[2010] */
|
||||
/* Karaoke Revolution (Wii)[2010] */
|
||||
if ((read_u32be(0x04,sf_h) == 0x00000800 ||
|
||||
read_u32be(0x04,sf_h) == 0x00000700) && /* rare? */
|
||||
read_u32be(0x08,sf_h) != 0x00000000 &&
|
||||
|
@ -730,11 +802,12 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
|
|||
strwav->codec = DSP;
|
||||
strwav->coefs_table = 0x7c;
|
||||
strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000;
|
||||
;VGM_LOG("STR+WAV: header TKGG/HOTDO/ASK (Wii)\n");
|
||||
;VGM_LOG("STR+WAV: header TKGG/HOTDO/ASK/KR (Wii)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The House of the Dead: Overkill (PS3)[2009] (not Blitz but still the same format) */
|
||||
/* Karaoke Revolution (PS3)[2010] */
|
||||
if ((read_u32be(0x04,sf_h) == 0x00000800 ||
|
||||
read_u32be(0x04,sf_h) == 0x00000700) && /* rare? */
|
||||
read_u32be(0x08,sf_h) != 0x00000000 &&
|
||||
|
@ -747,10 +820,30 @@ static int parse_header(STREAMFILE* sf_h, STREAMFILE* sf_b, strwav_header* strwa
|
|||
strwav->sample_rate = read_s32be(0x38,sf_h);
|
||||
strwav->flags = read_u32be(0x3c,sf_h);
|
||||
|
||||
strwav->channels = read_s32be(0x70,sf_h); /* tracks of 1ch */
|
||||
strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000;
|
||||
strwav->channels = read_s32be(0x70,sf_h);
|
||||
|
||||
/* other possibly-useful flags (see Karaoke Wii too):
|
||||
* - 0x4b: number of tracks
|
||||
* - 0x60: channels per track, ex. 020202 = 3 tracks of 2ch (max 0x08 = 8)
|
||||
* - 0xa0: sizes per track (max 0x20 = 8)
|
||||
* - 0xc0: samples per track (max 0x20 = 8)
|
||||
* - rest: info/seek table? */
|
||||
if (read_s32be(0x78,sf_h) != 0) { /* KRev */
|
||||
|
||||
strwav->tracks = strwav->channels / 2;
|
||||
strwav->num_samples = strwav->loop_end; /* num_samples here seems to be data size */
|
||||
strwav->interleave = read_s32be(0xA0,sf_h); /* one size per file, but CBR = same for all */
|
||||
//C0: stream samples (same as num_samples)
|
||||
|
||||
strwav->codec = MPEG; /* full CBR MP3 one after other */
|
||||
}
|
||||
else { /* HOTD */
|
||||
strwav->channels = read_s32be(0x70,sf_h); /* tracks of 1ch */
|
||||
strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000;
|
||||
|
||||
strwav->codec = PSX;
|
||||
}
|
||||
|
||||
strwav->codec = PSX;
|
||||
;VGM_LOG("STR+WAV: header HOTDO (PS3)\n");
|
||||
return 1;
|
||||
}
|
||||
|
|
21
Frameworks/vgmstream/vgmstream/src/meta/str_wav_streamfile.h
Normal file
21
Frameworks/vgmstream/vgmstream/src/meta/str_wav_streamfile.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#ifndef _STR_WAV_STREAMFILE_H_
|
||||
#define _STR_WAV_STREAMFILE_H_
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
/* Deblocks streams */
|
||||
static STREAMFILE* setup_str_wav_streamfile(STREAMFILE* sf, off_t stream_start, int stream_count, int stream_number, size_t interleave) {
|
||||
STREAMFILE *new_sf = NULL;
|
||||
deblock_config_t cfg = {0};
|
||||
|
||||
cfg.stream_start = stream_start;
|
||||
cfg.chunk_size = interleave;
|
||||
cfg.step_start = stream_number;
|
||||
cfg.step_count = stream_count;
|
||||
|
||||
/* setup sf */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -841,7 +841,7 @@ static void set_body_chunk(txth_header* txth) {
|
|||
if (!txth->sf_body)
|
||||
return;
|
||||
|
||||
/* treat chunks as subsongs */
|
||||
/* treat chunks as subsongs (less subsongs than chunks could be allowed to ignore some chunks but it's kinda odd) */
|
||||
if (txth->subsong_count > 1 && txth->subsong_count == txth->chunk_count)
|
||||
txth->chunk_number = txth->target_subsong;
|
||||
if (txth->chunk_number == 0)
|
||||
|
|
|
@ -1012,7 +1012,7 @@ static int parse_values(ubi_bao_header* bao) {
|
|||
}
|
||||
|
||||
/* set codec */
|
||||
if (bao->stream_type > 0x10) {
|
||||
if (bao->stream_type >= 0x10) {
|
||||
VGM_LOG("UBI BAO: unknown stream_type at %x\n", (uint32_t)bao->header_offset); goto fail;
|
||||
goto fail;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
#ifndef _UBI_BAO_STREAMFILE_H_
|
||||
#define _UBI_BAO_STREAMFILE_H_
|
||||
|
||||
//todo fix dupe code, but would be nice to keep it all in separate compilation units
|
||||
#include "ubi_sb_streamfile.h"
|
||||
|
||||
static STREAMFILE* setup_ubi_bao_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, int layer_number, int layer_count, int big_endian) {
|
||||
return setup_ubi_sb_streamfile(streamFile, stream_offset, stream_size, layer_number, layer_count, big_endian, 0);
|
||||
}
|
||||
|
||||
#endif /* _UBI_BAO_STREAMFILE_H_ */
|
||||
#ifndef _UBI_BAO_STREAMFILE_H_
|
||||
#define _UBI_BAO_STREAMFILE_H_
|
||||
|
||||
//todo fix dupe code, but would be nice to keep it all in separate compilation units
|
||||
#include "ubi_sb_streamfile.h"
|
||||
|
||||
static STREAMFILE* setup_ubi_bao_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, int layer_number, int layer_count, int big_endian) {
|
||||
return setup_ubi_sb_streamfile(streamFile, stream_offset, stream_size, layer_number, layer_count, big_endian, 0);
|
||||
}
|
||||
|
||||
#endif /* _UBI_BAO_STREAMFILE_H_ */
|
||||
|
|
|
@ -1818,6 +1818,7 @@ static int parse_type_audio(ubi_sb_header* sb, off_t offset, STREAMFILE* sf) {
|
|||
sb->sample_rate = read_32bit(offset + sb->cfg.audio_sample_rate, sf);
|
||||
sb->stream_type = read_32bit(offset + sb->cfg.audio_stream_type, sf);
|
||||
|
||||
//TO-DO a handful of SC:PT PS2 streams have 0 stream offset+size, maybe should set config + allow as dummies (ex. MAPS.SM1 #14191 #14255)
|
||||
if (sb->stream_size == 0) {
|
||||
VGM_LOG("UBI SB: bad stream size\n");
|
||||
goto fail;
|
||||
|
@ -2414,7 +2415,8 @@ static int parse_offsets(ubi_sb_header* sb, STREAMFILE* sf) {
|
|||
break;
|
||||
}
|
||||
|
||||
if (sb->stream_offset == 0) {
|
||||
/* valid in rare cases with ram-streamed but also external file (SC:PT PS2 > MAPS.RS1)*/
|
||||
if (sb->stream_offset == 0 && !sb->is_external) {
|
||||
VGM_LOG("UBI SM: Failed to find offset for resource %d in subblock %d in map %s\n", sb->header_index, sb->subblock_id, sb->map_name);
|
||||
goto fail;
|
||||
}
|
||||
|
|
|
@ -167,8 +167,8 @@ VGMSTREAM* init_vgmstream_vab(STREAMFILE* sf) {
|
|||
|
||||
data_size = read_u16le(waves_off + i * 0x02, sf) << 3;
|
||||
|
||||
if (data_size == 0 && center == 0 && shift == 0) {
|
||||
// hack for empty sounds in Critical Depth
|
||||
if (data_size == 0 /*&& center == 0 && shift == 0*/) {
|
||||
// hack for empty sounds in rare cases (may set center/shift to 0 as well) [Critical Depth]
|
||||
vgmstream = init_vgmstream_silence(1, 44100, 44100);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
|
|
|
@ -25,8 +25,9 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) {
|
|||
* .l/r: Crash Nitro Kart (PS2), Gradius V (PS2)
|
||||
* .vas: Kingdom Hearts II (PS2)
|
||||
* .xa2: Shikigami no Shiro (PS2)
|
||||
* .snd: Alien Breed (Vita) */
|
||||
if (!check_extensions(sf,"vag,swag,str,vig,l,r,vas,xa2,snd"))
|
||||
* .snd: Alien Breed (Vita)
|
||||
* .svg: ModernGroove: Ministry of Sound Edition (PS2) */
|
||||
if (!check_extensions(sf,"vag,swag,str,vig,l,r,vas,xa2,snd,svg"))
|
||||
return NULL;
|
||||
|
||||
file_size = get_streamfile_size(sf);
|
||||
|
@ -148,6 +149,16 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) {
|
|||
|
||||
loop_flag = ps_find_loop_offsets(sf, start_offset, channel_size*channels, channels, interleave, &loop_start_sample, &loop_end_sample);
|
||||
}
|
||||
else if (version == 0x00000020 && is_id32be(0x800,sf, "VAGp")) {
|
||||
/* ModernGroove: Ministry of Sound Edition (PS2) */
|
||||
start_offset = 0x30;
|
||||
channels = 2;
|
||||
interleave = 0x800;
|
||||
interleave_first = interleave - start_offset; /* includes header */
|
||||
interleave_first_skip = start_offset;
|
||||
|
||||
loop_flag = 0;
|
||||
}
|
||||
else if (version == 0x40000000) {
|
||||
/* Killzone (PS2) */
|
||||
start_offset = 0x30;
|
||||
|
|
57
Frameworks/vgmstream/vgmstream/src/meta/vig_kces.c
Normal file
57
Frameworks/vgmstream/vgmstream/src/meta/vig_kces.c
Normal file
|
@ -0,0 +1,57 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* .vig - from Konami/KCE Studio games [Pop'n Music 11~14 (PS2), Dance Dance Revolution SuperNova/X (PS2)] */
|
||||
VGMSTREAM* init_vgmstream_vig_kces(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
uint32_t data_offset, data_size;
|
||||
int loop_flag, channels, sample_rate, interleave;
|
||||
uint32_t loop_start, loop_end;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (read_u32be(0x00,sf) != 0x01006408)
|
||||
return NULL;
|
||||
/* .vig: actual extension from DDR exes */
|
||||
if (!check_extensions(sf, "vig"))
|
||||
return NULL;
|
||||
|
||||
/* note this is almost the same as GbTs, may be fused later */
|
||||
/* 04: null */
|
||||
data_offset = read_u32le(0x08,sf);
|
||||
data_size = read_u32le(0x0C,sf); /* without padding */
|
||||
loop_start = read_u32le(0x10,sf); /* (0x00 if not set) */
|
||||
loop_end = read_u32le(0x14,sf); /* (0x00 if not set) */
|
||||
sample_rate = read_s32le(0x18,sf);
|
||||
channels = read_s32le(0x1C,sf);
|
||||
/* 20: 0? */
|
||||
interleave = read_u32le(0x24,sf); /* 0 for mono */
|
||||
/* 30+: garbage from other data */
|
||||
|
||||
loop_flag = (loop_end > 0);
|
||||
loop_end += loop_start; /* loop region matches PS-ADPCM flags */
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = ps_bytes_to_samples(data_size, channels);
|
||||
vgmstream->loop_start_sample = ps_bytes_to_samples(loop_start, channels);
|
||||
vgmstream->loop_end_sample = ps_bytes_to_samples(loop_end, channels);
|
||||
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
|
||||
vgmstream->meta_type = meta_VIG_KCES;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, data_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* VIS - from Konami games [AirForce Delta Strike (PS2) (PS2)] */
|
||||
VGMSTREAM * init_vgmstream_vis(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
size_t data_size;
|
||||
int loop_flag, channel_count;
|
||||
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(streamFile,"vis") )
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x56495341) /* "VISA" */
|
||||
goto fail;
|
||||
|
||||
start_offset = 0x800;
|
||||
data_size = get_streamfile_size(streamFile) - start_offset;
|
||||
|
||||
loop_flag = read_32bitLE(0x18,streamFile);
|
||||
channel_count = read_32bitLE(0x20,streamFile); /* assumed */
|
||||
/* 0x1c: always 0x10 */
|
||||
/* 0x24: always 0x01 */
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_VIS;
|
||||
vgmstream->sample_rate = read_32bitLE(0x08,streamFile);
|
||||
vgmstream->num_samples = ps_bytes_to_samples(data_size,channel_count);
|
||||
vgmstream->loop_start_sample = ps_bytes_to_samples(read_32bitLE(0x0c,streamFile),channel_count);
|
||||
vgmstream->loop_end_sample = ps_bytes_to_samples(read_32bitLE(0x10,streamFile),channel_count);
|
||||
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = read_32bitLE(0x14,streamFile); /* usually 0x10 or 0x4000 */
|
||||
if (vgmstream->interleave_block_size)
|
||||
vgmstream->interleave_last_block_size =
|
||||
(data_size % (vgmstream->interleave_block_size*channel_count)) / channel_count;
|
||||
read_string(vgmstream->stream_name,0x10+1, 0x28,streamFile);
|
||||
|
||||
if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) )
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
|
@ -9,11 +9,11 @@ VGMSTREAM* init_vgmstream_waf(STREAMFILE* sf) {
|
|||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "waf"))
|
||||
goto fail;
|
||||
|
||||
if (!is_id32be(0x00,sf, "WAF\0"))
|
||||
goto fail;
|
||||
return NULL;
|
||||
if (!check_extensions(sf, "waf"))
|
||||
return NULL;
|
||||
|
||||
if (read_u32le(0x34,sf) + 0x38 != get_streamfile_size(sf))
|
||||
goto fail;
|
||||
|
||||
|
|
|
@ -575,6 +575,7 @@ VGMSTREAM* init_vgmstream_wwise_bnk(STREAMFILE* sf, int* p_prefetch) {
|
|||
switch(ww.channel_layout) {
|
||||
case mapping_7POINT1_surround: cfg.coupled_count = 3; break; /* 2ch+2ch+2ch+1ch+1ch, 5 streams */
|
||||
case mapping_5POINT1_surround: /* 2ch+2ch+1ch+1ch, 4 streams */
|
||||
case mapping_5POINT0_surround: /* 2ch+2ch+1ch, 3 streams [Bayonetta 3 (Switch)] */
|
||||
case mapping_QUAD_side: cfg.coupled_count = 2; break; /* 2ch+2ch, 2 streams */
|
||||
case mapping_2POINT1_xiph: /* 2ch+1ch, 2 streams */
|
||||
case mapping_STEREO: cfg.coupled_count = 1; break; /* 2ch, 1 stream */
|
||||
|
|
|
@ -3,16 +3,15 @@
|
|||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, off_t start, uint16_t* p_stream_config, off_t* p_stream_offset, size_t* p_stream_size, int* p_form2);
|
||||
static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, uint32_t start, uint32_t* p_stream_offset, uint32_t* p_stream_size);
|
||||
static int xa_check_format(STREAMFILE* sf, off_t offset, int is_blocked);
|
||||
|
||||
/* XA - from Sony PS1 and Philips CD-i CD audio */
|
||||
VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
uint32_t start_offset, stream_size = 0;
|
||||
int loop_flag = 0, channels, sample_rate, bps;
|
||||
int is_riff = 0, is_form2 = 0, is_blocked;
|
||||
size_t stream_size = 0;
|
||||
int total_subsongs = 0, target_subsong = sf->stream_index;
|
||||
uint16_t target_config = 0;
|
||||
|
||||
|
@ -31,7 +30,7 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
|
|||
}
|
||||
else {
|
||||
/* non-blocked (ISO 2048 mode1/data) or incorrectly ripped: use TXTH */
|
||||
goto fail;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* .xa: common
|
||||
|
@ -40,9 +39,10 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
|
|||
* .grn: Micro Machines (CDi)
|
||||
* .an2: Croc (PS1) movies
|
||||
* .xai: Quake II (PS1)
|
||||
* .no: Incredible Crisis (PS1)
|
||||
* (extensionless): bigfiles [Castlevania: Symphony of the Night (PS1)] */
|
||||
if (!check_extensions(sf,"xa,str,pxa,grn,an2,,xai"))
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
/* Proper XA comes in raw (BIN 2352 mode2/form2) CD sectors, that contain XA subheaders.
|
||||
* For headerless XA (ISO 2048 mode1/data) mode use TXTH. */
|
||||
|
@ -53,7 +53,7 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
|
|||
|
||||
/* find subsongs as XA can interleave sectors using 'file' and 'channel' makers (see blocked_xa.c) */
|
||||
if (/*!is_riff &&*/ is_blocked) {
|
||||
total_subsongs = xa_read_subsongs(sf, target_subsong, start_offset, &target_config, &start_offset, &stream_size, &is_form2);
|
||||
total_subsongs = xa_read_subsongs(sf, target_subsong, start_offset, &start_offset, &stream_size);
|
||||
if (total_subsongs <= 0) goto fail;
|
||||
}
|
||||
else {
|
||||
|
@ -63,34 +63,53 @@ VGMSTREAM* init_vgmstream_xa(STREAMFILE* sf) {
|
|||
/* data is ok: parse header */
|
||||
if (is_blocked) {
|
||||
/* parse 0x18 sector header (also see blocked_xa.c) */
|
||||
uint8_t xa_header = read_u8(start_offset + 0x13,sf);
|
||||
uint32_t curr_info = read_u32be(start_offset + 0x10, sf);
|
||||
uint16_t xa_config = (curr_info >> 16) & 0xFFFF; /* file+channel markers */
|
||||
uint8_t xa_submode = (curr_info >> 8) & 0xFF;
|
||||
uint8_t xa_header = (curr_info >> 0) & 0xFF;
|
||||
|
||||
/* header is repeated at 0x14 and could check if matches, but some ripped XA patch byte 0x01
|
||||
* for some reason, and in rare cases has garbage [Incredible Crisis (PS1) XAPACK00.NO#5]
|
||||
* (probably means a real PS1 only uses the first header, if it can play such XA) */
|
||||
|
||||
target_config = xa_config;
|
||||
is_form2 = (xa_submode & 0x20);
|
||||
|
||||
switch((xa_header >> 0) & 3) { /* 0..1: mono/stereo */
|
||||
case 0: channels = 1; break;
|
||||
case 1: channels = 2; break;
|
||||
default: goto fail;
|
||||
default:
|
||||
vgm_logi("XA: buggy data found\n");
|
||||
goto fail;
|
||||
}
|
||||
switch((xa_header >> 2) & 3) { /* 2..3: sample rate */
|
||||
case 0: sample_rate = 37800; break;
|
||||
case 1: sample_rate = 18900; break;
|
||||
default: goto fail;
|
||||
default:
|
||||
vgm_logi("XA: buggy data found\n");
|
||||
goto fail;
|
||||
}
|
||||
switch((xa_header >> 4) & 3) { /* 4..5: bits per sample */
|
||||
case 0: bps = 4; break; /* PS1 games only do 4-bit ADPCM */
|
||||
case 1: bps = 8; break; /* Micro Machines (CDi) */
|
||||
default: goto fail;
|
||||
default:
|
||||
vgm_logi("XA: buggy data found\n");
|
||||
goto fail;
|
||||
}
|
||||
switch((xa_header >> 6) & 1) { /* 6: emphasis flag (should apply a de-emphasis filter) */
|
||||
case 0: break;
|
||||
default: /* very rare, waveform looks ok so maybe not needed [Croc (PS1) PACKx.str] */
|
||||
default:
|
||||
/* very rare, waveform looks ok so maybe not needed [Croc (PS1) PACKx.str] */
|
||||
vgm_logi("XA: emphasis found\n");
|
||||
break;
|
||||
}
|
||||
switch((xa_header >> 7) & 1) { /* 7: reserved */
|
||||
case 0: break;
|
||||
default:
|
||||
vgm_logi("XA: unknown reserved bit found\n");
|
||||
goto fail;
|
||||
/* very rare, found in all regions and xa channel's headers but probably a mastering
|
||||
* bug since repeated header is wrong [Incredible Crisis (PS1) XAPACK00.NO#5] */
|
||||
vgm_logi("XA: reserved bit found\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -133,15 +152,16 @@ fail:
|
|||
|
||||
|
||||
static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked) {
|
||||
uint8_t frame_hdr[0x10];
|
||||
int i, j, sector = 0, skip = 0;
|
||||
off_t test_offset = offset;
|
||||
const size_t sector_size = (is_blocked ? 0x900 : 0x800);
|
||||
const size_t extra_size = (is_blocked ? 0x18 : 0x00);
|
||||
const size_t frame_size = 0x80;
|
||||
const int sector_max = 3;
|
||||
const int skip_max = 32; /* videos interleave 7 or 15 sectors + 1 audio sector, maybe 31 too */
|
||||
|
||||
uint8_t frame_hdr[0x10];
|
||||
int sector = 0, skip = 0;
|
||||
uint32_t test_offset = offset;
|
||||
|
||||
/* test frames inside CD sectors */
|
||||
while (sector < sector_max) {
|
||||
if (is_blocked) {
|
||||
|
@ -159,11 +179,11 @@ static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked) {
|
|||
|
||||
test_offset += extra_size; /* header */
|
||||
|
||||
for (i = 0; i < (sector_size / frame_size); i++) {
|
||||
for (int i = 0; i < (sector_size / frame_size); i++) {
|
||||
read_streamfile(frame_hdr, test_offset, sizeof(frame_hdr), sf);
|
||||
|
||||
/* XA frame checks: filter indexes should be 0..3, and shifts 0..C */
|
||||
for (j = 0; j < 16; j++) {
|
||||
for (int j = 0; j < 16; j++) {
|
||||
uint8_t header = get_u8(frame_hdr + j);
|
||||
if (((header >> 4) & 0xF) > 0x03)
|
||||
goto fail;
|
||||
|
@ -175,11 +195,10 @@ static int xa_check_format(STREAMFILE *sf, off_t offset, int is_blocked) {
|
|||
if (get_u32be(frame_hdr+0x00) != get_u32be(frame_hdr+0x04) ||
|
||||
get_u32be(frame_hdr+0x08) != get_u32be(frame_hdr+0x0c))
|
||||
goto fail;
|
||||
/* blank frames should always use 0x0c0c0c0c (due to how shift works) */
|
||||
if (get_u32be(frame_hdr+0x00) == 0 &&
|
||||
get_u32be(frame_hdr+0x04) == 0 &&
|
||||
get_u32be(frame_hdr+0x08) == 0 &&
|
||||
get_u32be(frame_hdr+0x0c) == 0)
|
||||
/* blank frames should always use 0x0c0c0c0c due to how shift works, (in rare file-channels some frames may be blank though) */
|
||||
if (i == 0 &&
|
||||
get_u32be(frame_hdr+0x00) == 0 && get_u32be(frame_hdr+0x04) == 0 &&
|
||||
get_u32be(frame_hdr+0x08) == 0 && get_u32be(frame_hdr+0x0c) == 0)
|
||||
goto fail;
|
||||
|
||||
test_offset += 0x80;
|
||||
|
@ -196,14 +215,11 @@ fail:
|
|||
}
|
||||
|
||||
|
||||
#define XA_SUBSONG_MAX 1024 /* +500 found in bigfiles like Castlevania SOTN */
|
||||
#define XA_MAX_CHANNELS 32 /* usually 08-16, seen ~24 in Langrisser V (PS1) */
|
||||
|
||||
typedef struct xa_subsong_t {
|
||||
uint16_t config;
|
||||
off_t start;
|
||||
int form2;
|
||||
int sectors;
|
||||
int end_flag;
|
||||
typedef struct {
|
||||
uint32_t info;
|
||||
int subsong;
|
||||
} xa_subsong_t;
|
||||
|
||||
/* Get subsong info, as real XA data interleaves N sectors/subsongs (often 8/16). Extractors deinterleave
|
||||
|
@ -211,17 +227,38 @@ typedef struct xa_subsong_t {
|
|||
* usable sectors for bytes-to-samples.
|
||||
*
|
||||
* Bigfiles that paste tons of XA together are slow to parse since we need to read every sector to
|
||||
* count totals, but XA subsong handling is mainly for educational purposes. */
|
||||
static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, off_t start, uint16_t* p_stream_config, off_t* p_stream_offset, size_t* p_stream_size, int* p_form2) {
|
||||
xa_subsong_t *cur_subsong = NULL;
|
||||
xa_subsong_t subsongs[XA_SUBSONG_MAX] = {0};
|
||||
* count totals, but XA subsong handling is mainly for educational purposes.
|
||||
*
|
||||
* Raw XA CD sectors are interleaved and classified into "files" and "channels" due to how CD driver/audio buffer works.
|
||||
* Devs select one file+channel (or just channel?) to play and CD ignores non-target sectors.
|
||||
* "files" can be any number in any order (but usually 00~64), and "channels" seem to be max 00~0F.
|
||||
* file+channel (=song) ends with a flag or when file changes; simplified example (upper=file, lower=channel):
|
||||
* 0101 0102 0101 0102 0201 0202 0201 0202 0101 0102
|
||||
* adapted to subsongs:
|
||||
* 0101 #1 (all 0101 sectors until file change = 2 sectors)
|
||||
* 0102 #2
|
||||
* 0201 #3
|
||||
* 0202 #4
|
||||
* 0101 #5 (different than first subsong since there was another file in between, 1 sector)
|
||||
* 0102 #6
|
||||
*
|
||||
* For video + audio the layout is the same with extra flags to detect video/audio sectors:
|
||||
* 0101v 0101v 0101v 0101v 0101v 0101v 0101v 0101a (usually 7 video sectors per 1 audio sector)
|
||||
*
|
||||
* CDs can't have 0101 0101 0101 ... audio sectors (need to interleave other channels, or repeat data),
|
||||
* but can be seen in demuxed XA. Combinations like a 0101 after 0201 probably only happen when devs
|
||||
* paste many XAs into a bigfile, which likely would jump via offsets in exe to the XA start (can be
|
||||
* split), but they are detected here for convenience.
|
||||
*/
|
||||
static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, uint32_t start, uint32_t* p_stream_offset, uint32_t* p_stream_size) {
|
||||
const size_t sector_size = 0x930;
|
||||
uint16_t prev_config;
|
||||
int i, subsongs_count = 0;
|
||||
size_t file_size;
|
||||
off_t offset;
|
||||
STREAMFILE *sf_test = NULL;
|
||||
uint8_t header[4];
|
||||
int subsongs_count = 0;
|
||||
uint32_t offset, file_size;
|
||||
STREAMFILE* sf_test = NULL;
|
||||
|
||||
xa_subsong_t xa_subsongs[XA_MAX_CHANNELS] = {0};
|
||||
uint32_t target_start = 0xFFFFFFFFu;
|
||||
int target_sectors = 0;
|
||||
|
||||
|
||||
/* buffer to speed up header reading; bigger (+0x8000) is actually faster than very small (~0x10),
|
||||
|
@ -229,99 +266,72 @@ static int xa_read_subsongs(STREAMFILE* sf, int target_subsong, off_t start, uin
|
|||
sf_test = reopen_streamfile(sf, 0x10000);
|
||||
if (!sf_test) goto fail;
|
||||
|
||||
prev_config = 0xFFFFu;
|
||||
file_size = get_streamfile_size(sf);
|
||||
offset = start;
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
|
||||
/* read XA sectors */
|
||||
while (offset < file_size) {
|
||||
uint16_t xa_config;
|
||||
uint8_t xa_submode;
|
||||
int is_audio, is_eof;
|
||||
uint32_t curr_info = read_u32be(offset + 0x10, sf_test);
|
||||
//uint8_t xa_file = (curr_info >> 24) & 0xFF;
|
||||
uint8_t xa_chan = (curr_info >> 16) & 0xFF;
|
||||
uint8_t xa_submode = (curr_info >> 8) & 0xFF;
|
||||
//uint8_t xa_header = (curr_info >> 0) & 0xFF;
|
||||
bool is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02);
|
||||
bool is_eof = (xa_submode & 0x80);
|
||||
bool is_target = false;
|
||||
|
||||
read_streamfile(header, offset + 0x10, sizeof(header), sf_test);
|
||||
xa_config = get_u16be(header + 0x00); /* file+channel markers */
|
||||
xa_submode = get_u8 (header + 0x02); /* flags */
|
||||
is_audio = !(xa_submode & 0x08) && (xa_submode & 0x04) && !(xa_submode & 0x02);
|
||||
is_eof = (xa_submode & 0x80);
|
||||
if (xa_chan >= XA_MAX_CHANNELS) {
|
||||
VGM_LOG("XA: too many channels: %x\n", xa_chan);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
VGM_ASSERT((xa_submode & 0x01), "XA: end of audio at %lx\n", offset); /* rare, signals last sector [Tetris (CD-i)] */
|
||||
//;VGM_ASSERT(is_eof, "XA: eof at %lx\n", offset);
|
||||
//;VGM_ASSERT(!is_audio, "XA: not audio at %lx\n", offset);
|
||||
//;VGM_ASSERT((xa_submode & 0x01), "XA: end of audio at %x\n", offset); /* rare, signals last sector [Tetris (CD-i), Langrisser V (PS1)] */
|
||||
//;VGM_ASSERT(is_eof, "XA: eof %02x%02x at %x\n", xa_file, xa_chan, offset); /* this sector still has data */
|
||||
//;VGM_ASSERT(!is_audio, "XA: not audio at %x\n", offset);
|
||||
|
||||
if (!is_audio) {
|
||||
offset += sector_size;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* detect file+channel change = sector of new subsong or existing subsong
|
||||
* (happens on every sector for interleaved XAs but only once for deint'd or video XAs) */
|
||||
if (xa_config != prev_config)
|
||||
{
|
||||
/* find if this sector/config belongs to known subsong that hasn't ended
|
||||
* (unsure if repeated configs+end flag is possible but probably in giant XAs) */
|
||||
cur_subsong = NULL;
|
||||
for (i = 0; i < subsongs_count; i++) { /* search algo could be improved, meh */
|
||||
if (subsongs[i].config == xa_config && !subsongs[i].end_flag) {
|
||||
cur_subsong = &subsongs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* use info without submode to detect new subsongs */
|
||||
curr_info = curr_info & 0xFFFF00FF;
|
||||
|
||||
/* old subsong not found = add new to list */
|
||||
if (cur_subsong == NULL) {
|
||||
uint8_t xa_channel = get_u8(header + 0x01);
|
||||
|
||||
/* when file+channel changes mark prev subsong of the same channel as finished
|
||||
* (this ensures reused ids in bigfiles are handled properly, ex.: 0101... 0201... 0101...) */
|
||||
for (i = subsongs_count - 1; i >= 0; i--) {
|
||||
uint16_t subsong_config = subsongs[i].config;
|
||||
uint8_t subsong_channel = subsong_config & 0xFF;
|
||||
if (xa_config != subsong_config && xa_channel == subsong_channel) {
|
||||
subsongs[i].end_flag = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cur_subsong = &subsongs[subsongs_count];
|
||||
subsongs_count++;
|
||||
if (subsongs_count >= XA_SUBSONG_MAX) goto fail;
|
||||
|
||||
cur_subsong->config = xa_config;
|
||||
cur_subsong->form2 = (xa_submode & 0x20);
|
||||
cur_subsong->start = offset;
|
||||
//cur_subsong->sectors = 0;
|
||||
//cur_subsong->end_flag = 0;
|
||||
|
||||
//;VGM_LOG("XA: new subsong %i with config %04x at %lx\n", subsongs_count, xa_config, offset);
|
||||
}
|
||||
|
||||
prev_config = xa_config;
|
||||
}
|
||||
else {
|
||||
if (cur_subsong == NULL) goto fail;
|
||||
/* changes for a current channel = new subsong */
|
||||
if (xa_subsongs[xa_chan].info != curr_info) {
|
||||
subsongs_count++;
|
||||
xa_subsongs[xa_chan].info = curr_info;
|
||||
xa_subsongs[xa_chan].subsong = subsongs_count;
|
||||
}
|
||||
|
||||
cur_subsong->sectors++;
|
||||
if (is_eof)
|
||||
cur_subsong->end_flag = 1;
|
||||
/* current channel is still from expected subsong */
|
||||
is_target = xa_subsongs[xa_chan].subsong == target_subsong;
|
||||
if (is_target) {
|
||||
if (target_start == 0xFFFFFFFFu)
|
||||
target_start = offset;
|
||||
target_sectors++;
|
||||
}
|
||||
|
||||
/* remove info (after handling sector) so next comparison for this channel adds a subsong */
|
||||
if (is_eof) {
|
||||
xa_subsongs[xa_chan].info = 0;
|
||||
xa_subsongs[xa_chan].subsong = 0;
|
||||
}
|
||||
|
||||
offset += sector_size;
|
||||
}
|
||||
|
||||
VGM_ASSERT(subsongs_count < 1, "XA: no audio found\n");
|
||||
VGM_ASSERT(subsongs_count < 1, "XA: no audio found\n"); /* probably not possible even in videos */
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > subsongs_count || subsongs_count < 1) goto fail;
|
||||
if (target_sectors == 0) goto fail;
|
||||
|
||||
cur_subsong = &subsongs[target_subsong - 1];
|
||||
*p_stream_config = cur_subsong->config;
|
||||
*p_stream_offset = cur_subsong->start;
|
||||
*p_stream_size = cur_subsong->sectors * sector_size;
|
||||
*p_form2 = cur_subsong->form2;
|
||||
*p_stream_offset = target_start;
|
||||
*p_stream_size = target_sectors * sector_size;
|
||||
|
||||
//;VGM_LOG("XA: subsong config=%x, offset=%lx, size=%x, form2=%i\n", *p_stream_config, *p_stream_offset, *p_stream_size, *p_form2);
|
||||
//;VGM_LOG("XA: subsong offset=%x, size=%x\n", *p_stream_offset, *p_stream_size);
|
||||
|
||||
close_streamfile(sf_test);
|
||||
return subsongs_count;
|
||||
|
|
|
@ -24,6 +24,13 @@ const char* filename_extension(const char* pathname) {
|
|||
}
|
||||
|
||||
|
||||
/* math helpers */
|
||||
uint32_t clamp_u32(uint32_t v, uint32_t min, uint32_t max) {
|
||||
if (v < min) return min;
|
||||
if (v > max) return max;
|
||||
return v;
|
||||
}
|
||||
|
||||
int round10(int val) {
|
||||
int round_val = val % 10;
|
||||
if (round_val < 5) /* half-down rounding */
|
||||
|
@ -32,6 +39,15 @@ int round10(int val) {
|
|||
return val + (10 - round_val);
|
||||
}
|
||||
|
||||
size_t align_size_to_block(size_t value, size_t block_align) {
|
||||
if (!block_align)
|
||||
return 0;
|
||||
|
||||
size_t extra_size = value % block_align;
|
||||
if (extra_size == 0) return value;
|
||||
return (value + block_align - extra_size);
|
||||
}
|
||||
|
||||
/* length is maximum length of dst. dst will always be null-terminated if
|
||||
* length > 0 */
|
||||
void concatn(int length, char * dst, const char * src) {
|
||||
|
@ -43,11 +59,10 @@ void concatn(int length, char * dst, const char * src) {
|
|||
dst[i]='\0';
|
||||
}
|
||||
|
||||
size_t align_size_to_block(size_t value, size_t block_align) {
|
||||
if (!block_align)
|
||||
return 0;
|
||||
|
||||
size_t extra_size = value % block_align;
|
||||
if (extra_size == 0) return value;
|
||||
return (value + block_align - extra_size);
|
||||
}
|
||||
bool check_subsongs(int* target_subsong, int total_subsongs) {
|
||||
if (*target_subsong == 0)
|
||||
*target_subsong = 1;
|
||||
if (*target_subsong < 0 || *target_subsong > total_subsongs || total_subsongs < 1)
|
||||
return false;
|
||||
return true;
|
||||
}
|
|
@ -48,14 +48,19 @@ static inline /*const*/ uint64_t get_id64be(const char* s) {
|
|||
|
||||
/* less common functions, no need to inline */
|
||||
|
||||
uint32_t clamp_u32(uint32_t v, uint32_t min, uint32_t max);
|
||||
|
||||
int round10(int val);
|
||||
|
||||
size_t align_size_to_block(size_t value, size_t block_align);
|
||||
|
||||
/* return a file's extension (a pointer to the first character of the
|
||||
* extension in the original filename or the ending null byte if no extension */
|
||||
const char* filename_extension(const char* pathname);
|
||||
|
||||
void concatn(int length, char * dst, const char * src);
|
||||
|
||||
size_t align_size_to_block(size_t value, size_t block_align);
|
||||
/* checks max subsongs and setups target */
|
||||
bool check_subsongs(int* target_subsong, int total_subsongs);
|
||||
|
||||
#endif
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue